@rebasepro/agent-skills 0.9.1-canary.fd3754b → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/rebase-admin/SKILL.md +30 -0
- package/skills/rebase-api/SKILL.md +9 -3
- package/skills/rebase-auth/SKILL.md +43 -5
- package/skills/rebase-backend-postgres/SKILL.md +10 -8
- package/skills/rebase-basics/SKILL.md +48 -7
- package/skills/rebase-collections/SKILL.md +3 -3
- package/skills/rebase-deployment/SKILL.md +38 -2
- package/skills/rebase-design-language/SKILL.md +8 -8
- package/skills/rebase-email/SKILL.md +4 -4
- package/skills/rebase-realtime/SKILL.md +124 -9
- package/skills/rebase-sdk/SKILL.md +59 -17
- package/skills/rebase-security/SKILL.md +216 -218
- package/skills/rebase-storage/SKILL.md +43 -4
- package/skills/rebase-studio/SKILL.md +2 -3
- package/skills/rebase-ui-components/SKILL.md +6 -6
- package/skills/rebase-webhooks/SKILL.md +32 -39
package/package.json
CHANGED
|
@@ -745,6 +745,36 @@ Views participate in the same navigation group system as collections. Use the `g
|
|
|
745
745
|
|
|
746
746
|
---
|
|
747
747
|
|
|
748
|
+
## 11. Where the Admin Lives — Mounting Under a URL Prefix
|
|
749
|
+
|
|
750
|
+
In the default scaffold the frontend **is** the admin panel: `App.tsx` renders `<RebaseAuth>` + `<RebaseAdmin>` + `<RebaseShell>` at the root, so the deployed URL shows the admin directly. There is no separate admin URL — in production one server serves both the API (`/api/*`) and this SPA.
|
|
751
|
+
|
|
752
|
+
When a project ships its **own product app** as the frontend, the admin is mounted under a prefix (commonly `/admin`) instead. Split the Vite entry by URL so each app is lazy-loaded and product visitors never download the admin bundle:
|
|
753
|
+
|
|
754
|
+
```tsx
|
|
755
|
+
// frontend/src/main.tsx
|
|
756
|
+
const isAdmin = window.location.pathname.startsWith("/admin");
|
|
757
|
+
const ProductApp = lazy(() => import("./App"));
|
|
758
|
+
const AdminApp = lazy(() => import("./AdminApp"));
|
|
759
|
+
|
|
760
|
+
if (isAdmin) {
|
|
761
|
+
// The admin uses useBlocker → needs a DATA router
|
|
762
|
+
const router = createBrowserRouter([{ path: "/admin/*", element: <AdminApp /> }]);
|
|
763
|
+
root.render(<RouterProvider router={router} />);
|
|
764
|
+
} else {
|
|
765
|
+
root.render(<BrowserRouter><ProductApp /></BrowserRouter>);
|
|
766
|
+
}
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
```tsx
|
|
770
|
+
// frontend/src/AdminApp.tsx — tell the CMS about the prefix
|
|
771
|
+
<RebaseAdmin collections={collections} basePath="/admin" />
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
> **IMPORTANT FOR AGENTS:** Choose **either** a router `basename="/admin"` **or** `<RebaseAdmin basePath="/admin">` — never both, or the prefix is applied twice. Without either, collection views hang on a spinner because URL⇄collection resolution never matches. `<RebaseAdmin>` requires a **data router** (`createBrowserRouter`) because it uses `useBlocker`.
|
|
775
|
+
|
|
776
|
+
---
|
|
777
|
+
|
|
748
778
|
## Exported Components
|
|
749
779
|
|
|
750
780
|
The admin package exports the following components (from `@rebasepro/admin`):
|
|
@@ -337,11 +337,17 @@ Authorization: Bearer rk_live_abc123...
|
|
|
337
337
|
|
|
338
338
|
If an API key lacks the required permission for an operation, a `403 API_KEY_FORBIDDEN` error is returned.
|
|
339
339
|
|
|
340
|
-
|
|
340
|
+
Beyond collections, the permission list also covers custom functions
|
|
341
|
+
(`"functions"` for all, `"functions/<name>"` for one) and file storage
|
|
342
|
+
(`"storage"`); the global `"*"` wildcard grants all three. API keys do NOT
|
|
343
|
+
bypass RLS: admin keys pass via the built-in admin policies, while non-admin
|
|
344
|
+
keys only see rows a security rule grants to the `service` role or the public.
|
|
345
|
+
|
|
346
|
+
**Admin API keys** — set `"admin": true` when creating a key to grant it the `admin` role. This gives access to all `/api/admin/*` routes (schema, users, other API keys, etc.) plus cron, backups, and logs. Use this for agents, MCP servers, and CI pipelines:
|
|
341
347
|
|
|
342
348
|
```bash
|
|
343
|
-
# CLI
|
|
344
|
-
rebase api-keys create --name "My Agent" --admin
|
|
349
|
+
# CLI (an explicit scope is required: --permissions '<json>' or --full-access)
|
|
350
|
+
rebase api-keys create --name "My Agent" --admin --full-access
|
|
345
351
|
|
|
346
352
|
# REST
|
|
347
353
|
POST /api/admin/api-keys
|
|
@@ -219,11 +219,48 @@ Twitter uses OAuth 2.0 with PKCE. The client must send `codeVerifier` alongside
|
|
|
219
219
|
|
|
220
220
|
### OAuth Account Linking
|
|
221
221
|
|
|
222
|
-
When an OAuth user signs in
|
|
223
|
-
|
|
224
|
-
|
|
222
|
+
When an OAuth user signs in via `POST /api/auth/{provider}`:
|
|
223
|
+
|
|
224
|
+
1. If an identity record exists for `(provider, providerId)` → log in that user. The email is not consulted.
|
|
225
|
+
2. If no identity exists but a user with the same email exists:
|
|
226
|
+
- **The provider asserted `emailVerified: true`** → **link** the provider to the existing account and log in as that user. One account, two sign-in methods.
|
|
227
|
+
- **The provider did NOT verify the email** → reject with `403 EMAIL_NOT_VERIFIED`. Nothing is created or modified.
|
|
225
228
|
3. If neither exists → create a new user, link the identity, assign `defaultRole`.
|
|
226
229
|
|
|
230
|
+
> **IMPORTANT FOR AGENTS:** A second account is **never** silently created for
|
|
231
|
+
> an email that already exists. If asked "does signing in with Google create a
|
|
232
|
+
> duplicate user?", the answer is no — it either links (verified) or errors
|
|
233
|
+
> (unverified). This is **not configurable**; there is deliberately no option to
|
|
234
|
+
> auto-link on unverified emails, because that would let anyone who can make a
|
|
235
|
+
> provider emit an address they don't own take over the matching account.
|
|
236
|
+
> Google always asserts `email_verified` for real Google accounts, so linking
|
|
237
|
+
> is the normal path for Google sign-in.
|
|
238
|
+
|
|
239
|
+
### Linking a Provider to a Signed-In Account
|
|
240
|
+
|
|
241
|
+
`POST /api/auth/link/{provider}` attaches a provider identity to the **already
|
|
242
|
+
authenticated** account (requires `Authorization: Bearer <token>`). The body is
|
|
243
|
+
the same payload the provider's sign-in route takes, e.g. `{ idToken }`.
|
|
244
|
+
|
|
245
|
+
This is the escape hatch from an `EMAIL_NOT_VERIFIED` rejection, and the way to
|
|
246
|
+
attach a provider whose email differs from the account's.
|
|
247
|
+
|
|
248
|
+
Unlike sign-in, linking here does **not** require a verified email and does not
|
|
249
|
+
require the emails to match — on sign-in the provider's email is the only
|
|
250
|
+
evidence tying the identity to an account, whereas here the caller has already
|
|
251
|
+
proven ownership by holding a valid session.
|
|
252
|
+
|
|
253
|
+
- `409 IDENTITY_ALREADY_LINKED` if that provider identity belongs to another user.
|
|
254
|
+
- Idempotent (`alreadyLinked: true`) if already linked to the caller.
|
|
255
|
+
|
|
256
|
+
### Adding a Password to a Provider-Only Account
|
|
257
|
+
|
|
258
|
+
A user who signed up via OAuth has no `passwordHash`:
|
|
259
|
+
|
|
260
|
+
- `POST /auth/register` with the same email → `409 EMAIL_EXISTS`.
|
|
261
|
+
- `POST /auth/change-password` → `400 INVALID_ACCOUNT` (no existing password to verify).
|
|
262
|
+
- **`forgot-password` → `reset-password` is the supported path.** It re-proves ownership of the address by email, after which the account has both sign-in methods.
|
|
263
|
+
|
|
227
264
|
### Custom OAuth Provider
|
|
228
265
|
|
|
229
266
|
You can register any OAuth provider by implementing the `OAuthProvider<T>` interface:
|
|
@@ -468,7 +505,7 @@ By default API keys get the `service` role (data access only). Set `"admin": tru
|
|
|
468
505
|
|
|
469
506
|
```bash
|
|
470
507
|
# CLI — create an admin API key
|
|
471
|
-
rebase api-keys create --name "My Agent" --admin
|
|
508
|
+
rebase api-keys create --name "My Agent" --admin --full-access
|
|
472
509
|
|
|
473
510
|
# REST
|
|
474
511
|
curl -X POST http://localhost:3000/api/admin/api-keys \
|
|
@@ -624,7 +661,7 @@ rebase api-keys list
|
|
|
624
661
|
rebase api-keys create --name "Read Only" --permissions '[{"collection":"orders","operations":["read"]}]'
|
|
625
662
|
|
|
626
663
|
# Create an admin key (for agents / MCP / CI)
|
|
627
|
-
rebase api-keys create --name "My Agent" --admin
|
|
664
|
+
rebase api-keys create --name "My Agent" --admin --full-access
|
|
628
665
|
|
|
629
666
|
# Revoke a key
|
|
630
667
|
rebase api-keys revoke <key-id>
|
|
@@ -659,6 +696,7 @@ All auth endpoints are mounted under `/api/auth`. Admin endpoints are under `/ap
|
|
|
659
696
|
| `PATCH` | `/auth/me` | Required | Update profile. Body: `{ displayName?, photoURL? }`. |
|
|
660
697
|
| `POST` | `/auth/change-password` | Required | Change password. Body: `{ oldPassword, newPassword }`. Invalidates all sessions. |
|
|
661
698
|
| `POST` | `/auth/send-verification` | Required | Send email verification link. Requires email service. |
|
|
699
|
+
| `POST` | `/auth/link/{provider}` | Required | Link an OAuth provider to the current account. Body: the provider's sign-in payload (e.g. `{ idToken }`). `409 IDENTITY_ALREADY_LINKED` if it belongs to another user. |
|
|
662
700
|
| `GET` | `/auth/sessions` | Required | List active sessions (refresh tokens). |
|
|
663
701
|
| `DELETE` | `/auth/sessions` | Required | Revoke all sessions (remote logout). |
|
|
664
702
|
| `DELETE` | `/auth/sessions/:id` | Required | Revoke a specific session. |
|
|
@@ -95,7 +95,7 @@ const { db, pool, connectionString } = createPostgresDatabaseConnection(
|
|
|
95
95
|
|
|
96
96
|
**Signature:**
|
|
97
97
|
|
|
98
|
-
```typescript
|
|
98
|
+
```typescript no-verify
|
|
99
99
|
function createPostgresDatabaseConnection(
|
|
100
100
|
connectionString: string,
|
|
101
101
|
schema?: Record<string, unknown>,
|
|
@@ -120,7 +120,7 @@ const readResources = createReadReplicaConnection(
|
|
|
120
120
|
|
|
121
121
|
**Signature:**
|
|
122
122
|
|
|
123
|
-
```typescript
|
|
123
|
+
```typescript no-verify
|
|
124
124
|
function createReadReplicaConnection(
|
|
125
125
|
connectionString: string,
|
|
126
126
|
schema?: Record<string, unknown>,
|
|
@@ -145,7 +145,7 @@ const directResources = createDirectDatabaseConnection(
|
|
|
145
145
|
|
|
146
146
|
**Signature:**
|
|
147
147
|
|
|
148
|
-
```typescript
|
|
148
|
+
```typescript no-verify
|
|
149
149
|
function createDirectDatabaseConnection(
|
|
150
150
|
connectionString: string,
|
|
151
151
|
schema?: Record<string, unknown>,
|
|
@@ -327,12 +327,13 @@ The `initializeRebaseBackend()` coordinator calls bootstrapper methods in this o
|
|
|
327
327
|
| `auth` | `RebaseAuthConfig \| AuthAdapter` | — | Authentication configuration or external adapter |
|
|
328
328
|
| `storage` | `BackendStorageConfig \| StorageController \| Record<string, ...>` | — | File storage configuration |
|
|
329
329
|
| `history` | `true \| { retention?: number }` | — | Enable entity audit trail. Pass `true` or `{ retention: 90 }` for TTL in days |
|
|
330
|
-
| `defaultSecurityRules` | `SecurityRule[]` | — | Default RLS rules for collections without explicit rules |
|
|
331
330
|
| `enableSwagger` | `boolean` | `true` | Enable OpenAPI/Swagger documentation |
|
|
332
331
|
| `cronPersistence` | `boolean` | `true` | Persist cron execution logs to database |
|
|
333
332
|
| `maxBodySize` | `number` | `10485760` (10MB) | Max request body size in bytes. `0` to disable |
|
|
334
333
|
| `csrf` | `{ origin: string \| string[] \| (origin: string) => boolean }` | — | CSRF protection (opt-in, disabled by default) |
|
|
335
|
-
| `
|
|
334
|
+
| `callbacks` | `CollectionCallbacks` | — | Global lifecycle callbacks applied to every collection, on every data path (REST, realtime, server-side `rebase.data`) |
|
|
335
|
+
| `mode` | `"cms" \| "baas"` | `"cms"` | `baas` derives collections from the live database at boot instead of config files, and turns the schema editor off |
|
|
336
|
+
| `baas` | `BaasOptions` | — | `baas` mode only: `{ unprotectedTables?: "exclude" \| "serve" }`. Default `"exclude"` — a table without RLS has no authorization model, so serving it would hand every row to every logged-in user. `"serve"` overrides, for trusted callers only |
|
|
336
337
|
| `logging` | `{ level?: "error" \| "warn" \| "info" \| "debug" }` | — | Log level configuration |
|
|
337
338
|
|
|
338
339
|
### Auth Configuration (RebaseAuthConfig)
|
|
@@ -386,7 +387,8 @@ export const env = loadEnv();
|
|
|
386
387
|
|
|
387
388
|
```typescript
|
|
388
389
|
import dotenv from "dotenv";
|
|
389
|
-
import { loadEnv
|
|
390
|
+
import { loadEnv } from "@rebasepro/server";
|
|
391
|
+
import { z } from "zod";
|
|
390
392
|
|
|
391
393
|
dotenv.config({ path: "../../.env" });
|
|
392
394
|
|
|
@@ -436,9 +438,9 @@ In non-production mode, `loadEnv()` **auto-generates** ephemeral `JWT_SECRET` an
|
|
|
436
438
|
| `ALLOW_LOCALHOST_IN_PRODUCTION` | No | `false` | Allow localhost URLs in production env vars |
|
|
437
439
|
| `CORS_ORIGINS` | No (⚠ required in prod) | — | Comma-separated allowed origins |
|
|
438
440
|
| `FRONTEND_URL` | No | — | Frontend URL (used for CORS) |
|
|
439
|
-
| `STORAGE_TYPE` | No | `local` | `local` or `
|
|
441
|
+
| `STORAGE_TYPE` | No | `local` | `local`, `s3`, or `gcs` |
|
|
440
442
|
| `STORAGE_PATH` | No | — | Path for local file storage |
|
|
441
|
-
| `FORCE_LOCAL_STORAGE` | No | `false` |
|
|
443
|
+
| `FORCE_LOCAL_STORAGE` | No | `false` | Allow local storage in production — **the backend refuses to boot without it** |
|
|
442
444
|
| `S3_BUCKET` | No (if s3) | — | S3 bucket name |
|
|
443
445
|
| `S3_REGION` | No | — | S3 region |
|
|
444
446
|
| `S3_ACCESS_KEY_ID` | No (if s3) | — | S3 access key |
|
|
@@ -168,12 +168,28 @@ A BaaS install is `server` + a driver + `client`, with no React in the tree.
|
|
|
168
168
|
|
|
169
169
|
| Option | Alias | Description |
|
|
170
170
|
|--------|-------|-------------|
|
|
171
|
+
| `--flavor` | `-f` | `baas` or `cms`. Picks what you get — see below. Prompts when omitted |
|
|
172
|
+
| `--template` | `-t` | Starter template to scaffold from |
|
|
171
173
|
| `--git` | `-g` | Initialize a git repository |
|
|
172
174
|
| `--install` | `-i` | Install dependencies with the detected PM |
|
|
173
175
|
| `--database-url` | — | PostgreSQL connection string (skip prompt) |
|
|
174
176
|
| `--introspect` | — | Auto-introspect the database after init |
|
|
175
177
|
| `--yes` | `-y` | Non-interactive mode (use all defaults) |
|
|
176
178
|
|
|
179
|
+
#### Flavors
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
rebase init my-api --flavor baas # backend/ alone — REST, auth, storage, realtime, backups. No config files, no UI, no React
|
|
183
|
+
rebase init my-app --flavor cms # config/ + backend/ + frontend/ — BaaS plus the admin panel (default)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
`baas` introspects the collections from the database at boot rather than reading
|
|
187
|
+
config files, so there is nothing to declare and nothing to keep in sync. It
|
|
188
|
+
serves only tables with `ENABLE ROW LEVEL SECURITY` — a table without RLS has no
|
|
189
|
+
authorization model, so serving it would hand every row to every logged-in user.
|
|
190
|
+
|
|
191
|
+
`dev`, `build` and `start` detect a missing `frontend/` and run backend-only.
|
|
192
|
+
|
|
177
193
|
```bash
|
|
178
194
|
# Interactive mode
|
|
179
195
|
rebase init my-app
|
|
@@ -392,7 +408,7 @@ When `extend` is provided, the base `rebaseEnvSchema` is merged (`.merge()`) wit
|
|
|
392
408
|
| `DB_POOL_CONNECT_TIMEOUT` | `string` → `number` | `"10000"` | No | Pool connect timeout (ms) |
|
|
393
409
|
| `STORAGE_TYPE` | `"local" \| "s3" \| "gcs"` | `"local"` | No | File storage backend type |
|
|
394
410
|
| `STORAGE_PATH` | `string` | — | No | Local storage directory path |
|
|
395
|
-
| `FORCE_LOCAL_STORAGE` | `"true" \| "false"` | — | No |
|
|
411
|
+
| `FORCE_LOCAL_STORAGE` | `"true" \| "false"` | — | No | Allow local storage in production — **the backend refuses to boot without it**, since the container filesystem loses uploads on restart |
|
|
396
412
|
| `S3_BUCKET` | `string` | — | When S3 | S3 bucket name |
|
|
397
413
|
| `S3_REGION` | `string` | — | When S3 | S3 region |
|
|
398
414
|
| `S3_ACCESS_KEY_ID` | `string` | — | When S3 | S3 access key |
|
|
@@ -436,6 +452,7 @@ import { initializeRebaseBackend, RebaseBackendConfig } from "@rebasepro/server"
|
|
|
436
452
|
|----------|------|---------|-------------|
|
|
437
453
|
| `server` | `Server` (Node `http.Server`) | — | **Required.** The HTTP server instance |
|
|
438
454
|
| `app` | `Hono<HonoEnv>` | — | **Required.** The Hono application instance |
|
|
455
|
+
| `mode` | `"cms" \| "baas"` | `"cms"` | How much of Rebase to run. `cms` takes collections from `collections`/`collectionsDir`; `baas` takes no collection config at all and derives them from the live database at boot, so every RLS-protected table is served with nothing to define. The schema editor is off in `baas` — it exists to write collection files back to disk. Both serve the same control plane (auth, storage, realtime, backups, cron, functions, OpenAPI); neither serves the admin SPA, which is the application's call via `serveSPA` |
|
|
439
456
|
| `collections` | `CollectionConfig[]` | `[]` | Inline collection definitions |
|
|
440
457
|
| `collectionsDir` | `string` | — | Directory to auto-discover collection files (used if `collections` is empty) |
|
|
441
458
|
| `basePath` | `string` | `"/api"` | Base path for all API routes |
|
|
@@ -445,14 +462,16 @@ import { initializeRebaseBackend, RebaseBackendConfig } from "@rebasepro/server"
|
|
|
445
462
|
| `auth` | `RebaseAuthConfig \| AuthAdapter` | — | Authentication config or pluggable adapter |
|
|
446
463
|
| `storage` | `BackendStorageConfig \| StorageController \| Record<string, ...>` | — | File storage configuration. Supports `"local"`, `"s3"`, and `"gcs"` (GCS/Firebase Storage) backends. Use `Record<string, StorageController>` for multi-backend setups with named sources |
|
|
447
464
|
| `history` | `unknown` | — | Entity history/audit-log configuration |
|
|
448
|
-
| `defaultSecurityRules` | `SecurityRule[]` | — | Default RLS rules for collections without their own |
|
|
449
465
|
| `enableSwagger` | `boolean` | `true` | Enable OpenAPI spec at `/api/docs` and Swagger UI at `/api/swagger` (dev only) |
|
|
450
466
|
| `functionsDir` | `string` | — | Directory for auto-discovered custom function handlers |
|
|
451
467
|
| `cronsDir` | `string` | — | Directory for auto-discovered cron job handlers |
|
|
452
468
|
| `cronPersistence` | `boolean` | `true` | Persist cron job execution logs to the database |
|
|
453
469
|
| `maxBodySize` | `number` | `10485760` (10 MB) | Max request body size in bytes. Set `0` to disable |
|
|
454
470
|
| `csrf` | `{ origin: string \| string[] \| ((origin: string) => boolean) }` | — | CSRF protection (opt-in, disabled by default) |
|
|
455
|
-
| `
|
|
471
|
+
| `callbacks` | `CollectionCallbacks` | — | Global lifecycle callbacks applied to every collection. Same type as per-collection `callbacks`, and fires on **every** data path (REST, realtime/WebSocket, server-side `rebase.data`). Order: global → collection → property |
|
|
472
|
+
| `baas` | `BaasOptions` | — | `baas` mode only: `{ unprotectedTables?: "exclude" \| "serve" }`. Default `"exclude"` — a table with RLS disabled carries no authorization model, and every authenticated request runs as `rebase_user`, so serving one hands every row to every logged-in user. Excluded tables are logged with the SQL to protect them. `"serve"` serves them anyway; only sensible when every caller is already trusted |
|
|
473
|
+
| `schemaEditor` | `boolean` | — | Force the schema-editor routes on or off. Defaults to enabled when `collectionsDir` is set, outside production, in `cms` mode |
|
|
474
|
+
| `storageSources` | `StorageSourceDefinition[]` | — | Named storage backends for multi-source setups. Each has a `key` that collection properties point at via `StorageConfig.storageSource` |
|
|
456
475
|
| `logging` | `{ level?: "error" \| "warn" \| "info" \| "debug" }` | `"info"` | Log level configuration |
|
|
457
476
|
|
|
458
477
|
> **IMPORTANT FOR AGENTS:** `maxBodySize` applies to all API routes under `basePath`. Storage upload routes have their **own** limit derived from the storage config's `maxFileSize` property (default: 50 MB), which overrides the global limit.
|
|
@@ -554,15 +573,37 @@ await initializeRebaseBackend({
|
|
|
554
573
|
// Single backend: { type: "local" | "s3" | "gcs" }
|
|
555
574
|
// Multi-backend: Record<string, StorageController> with named sources
|
|
556
575
|
storage: { type: env.STORAGE_TYPE },
|
|
557
|
-
defaultSecurityRules: [
|
|
558
|
-
{ operation: "select", access: "public" },
|
|
559
|
-
{ operations: ["insert", "update", "delete"], roles: ["admin"] },
|
|
560
|
-
],
|
|
561
576
|
});
|
|
562
577
|
|
|
563
578
|
console.log(`Server running at http://localhost:${env.PORT}`);
|
|
564
579
|
```
|
|
565
580
|
|
|
581
|
+
## Default security rules live with the collections, not here
|
|
582
|
+
|
|
583
|
+
`defaultSecurityRules` is **not** a `RebaseBackendConfig` option. Declare it in
|
|
584
|
+
`config/collections/index.ts`, beside the collections it applies to:
|
|
585
|
+
|
|
586
|
+
```typescript
|
|
587
|
+
// config/collections/index.ts
|
|
588
|
+
import type { SecurityRule } from "@rebasepro/types";
|
|
589
|
+
|
|
590
|
+
export const defaultSecurityRules: SecurityRule[] = [
|
|
591
|
+
{ operation: "select", access: "public" },
|
|
592
|
+
{ operations: ["insert", "update", "delete"], roles: ["admin"] }
|
|
593
|
+
];
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
Any collection in that directory that declares no `securityRules` inherits these;
|
|
597
|
+
one that declares its own keeps them; one with neither is **locked by default**
|
|
598
|
+
(admin-only).
|
|
599
|
+
|
|
600
|
+
It belongs there because `db push` generates the Postgres policies — the only
|
|
601
|
+
thing that actually enforces access — from the collection *files*, and never sees
|
|
602
|
+
the running server. A default on the backend config could never reach the
|
|
603
|
+
database while reading exactly like an authorization setting. In `baas` mode there
|
|
604
|
+
are no collection files and no `db push`, so the database's own RLS is the whole
|
|
605
|
+
model and there is nothing to default.
|
|
606
|
+
|
|
566
607
|
# The `rebase` Singleton
|
|
567
608
|
|
|
568
609
|
After `initializeRebaseBackend()` completes, a server-side singleton is available:
|
|
@@ -495,7 +495,7 @@ validation: {
|
|
|
495
495
|
|
|
496
496
|
Maps store nested objects as `JSONB` in PostgreSQL. They can define their own inner properties schema.
|
|
497
497
|
|
|
498
|
-
```typescript
|
|
498
|
+
```typescript no-verify
|
|
499
499
|
address: {
|
|
500
500
|
name: "Address",
|
|
501
501
|
type: "map",
|
|
@@ -663,7 +663,7 @@ Every property supports a `validation` object with these common options:
|
|
|
663
663
|
|
|
664
664
|
Use the `enum` property on `string` or `number` types to define picklist options:
|
|
665
665
|
|
|
666
|
-
```typescript
|
|
666
|
+
```typescript no-verify
|
|
667
667
|
status: {
|
|
668
668
|
name: "Status",
|
|
669
669
|
type: "string",
|
|
@@ -687,7 +687,7 @@ Each `EnumValueConfig` entry supports:
|
|
|
687
687
|
|
|
688
688
|
`EnumValues` can also be a `Record<string, string | EnumValueConfig>` for simpler definitions:
|
|
689
689
|
|
|
690
|
-
```typescript
|
|
690
|
+
```typescript no-verify
|
|
691
691
|
enum: {
|
|
692
692
|
draft: "Draft",
|
|
693
693
|
published: { label: "Published", color: "green" }
|
|
@@ -38,6 +38,36 @@ rebase deploy
|
|
|
38
38
|
rebase deploy --env dev
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
+
### What a Cloud Deployment Serves
|
|
42
|
+
|
|
43
|
+
`rebase deploy` ships **one container** per project, served at `https://<project>.apps.rebase.pro`. That container runs your backend, which handles:
|
|
44
|
+
|
|
45
|
+
- **`/api/*`** — the data API, auth, realtime, storage
|
|
46
|
+
- **everything else** — your built `frontend/` as a static SPA (via `serveSPA()`, see below)
|
|
47
|
+
|
|
48
|
+
There is **no separate admin URL** — the admin panel is part of your frontend, so where it appears depends on what your frontend is:
|
|
49
|
+
|
|
50
|
+
| Project type | What the root URL shows | Where the admin is |
|
|
51
|
+
|--------------|------------------------|--------------------|
|
|
52
|
+
| Default scaffold (`rebase init`, cms flavor) | The admin panel itself (login / bootstrap) | `/` — the frontend **is** the admin |
|
|
53
|
+
| Custom product frontend | Your product app | Wherever you mount it — commonly `/admin` (see below) |
|
|
54
|
+
| Backend-only (`--flavor backend`) | Nothing (API only) | Not deployed |
|
|
55
|
+
|
|
56
|
+
> **IMPORTANT FOR AGENTS:** On the **first visit** to a freshly deployed project's admin, Rebase shows the bootstrap screen ("Create your admin account"). The earliest-registered account receives admin privileges — the project owner should claim it immediately after deploying. Never fill this form on the user's behalf.
|
|
57
|
+
|
|
58
|
+
**Mounting the admin at `/admin` alongside a product app** — a single Vite entry can serve both, split by URL, so visitors never download the admin bundle:
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
// frontend/src/main.tsx
|
|
62
|
+
const isAdmin = window.location.pathname.startsWith("/admin");
|
|
63
|
+
const ProductApp = lazy(() => import("./App"));
|
|
64
|
+
const AdminApp = lazy(() => import("./AdminApp")); // renders <RebaseAdmin basePath="/admin" .../>
|
|
65
|
+
|
|
66
|
+
// route "/admin/*" → AdminApp, everything else → ProductApp
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Set **either** a router `basename="/admin"` **or** `<RebaseAdmin basePath="/admin">` — not both, or the prefix is applied twice.
|
|
70
|
+
|
|
41
71
|
---
|
|
42
72
|
|
|
43
73
|
## Docker (Self-Hosted)
|
|
@@ -318,7 +348,8 @@ Use the `extend` option to add your own typed env variables on top of the base R
|
|
|
318
348
|
|
|
319
349
|
```typescript
|
|
320
350
|
import dotenv from "dotenv";
|
|
321
|
-
import { loadEnv
|
|
351
|
+
import { loadEnv } from "@rebasepro/server";
|
|
352
|
+
import { z } from "zod";
|
|
322
353
|
|
|
323
354
|
dotenv.config({ path: "../../.env" });
|
|
324
355
|
|
|
@@ -369,11 +400,16 @@ All variables recognized by the base `rebaseEnvSchema`:
|
|
|
369
400
|
| `DATABASE_READ_URL` | `string` (URL) | No | — | Read replica database URL |
|
|
370
401
|
| `STORAGE_TYPE` | `enum` | No | `"local"` | `"local"`, `"s3"`, or `"gcs"` |
|
|
371
402
|
| `STORAGE_PATH` | `string` | No | — | Local file storage directory |
|
|
372
|
-
| `FORCE_LOCAL_STORAGE` | `optionalBoolString` | No | `undefined` → `false` |
|
|
403
|
+
| `FORCE_LOCAL_STORAGE` | `optionalBoolString` | No | `undefined` → `false` | Allow `STORAGE_TYPE=local` in production — **the backend refuses to boot without it** |
|
|
373
404
|
| `S3_BUCKET` | `string` | If S3 | — | S3 bucket name |
|
|
374
405
|
| `S3_REGION` | `string` | No | — | S3 region |
|
|
375
406
|
| `S3_ACCESS_KEY_ID` | `string` | If S3 | — | S3 access key |
|
|
376
407
|
| `S3_SECRET_ACCESS_KEY` | `string` | If S3 | — | S3 secret key |
|
|
408
|
+
| `GCS_BUCKET` | `string` | If GCS | — | GCS bucket name |
|
|
409
|
+
| `GCS_PROJECT_ID` | `string` | No | — | GCP project (auto-detected from credentials) |
|
|
410
|
+
| `GCS_KEY_FILENAME` | `string` | No | — | Service-account key file; omit on GKE (Workload Identity/ADC) |
|
|
411
|
+
|
|
412
|
+
> **Deploying with `STORAGE_TYPE=local` fails to boot in production.** Local storage is the container filesystem, so uploads are destroyed on the next restart — silently, with no error at write or read time. Set `STORAGE_TYPE=s3`/`gcs`, or `FORCE_LOCAL_STORAGE=true` if a durable volume really is mounted at `STORAGE_PATH`. A crashed rollout is recoverable; lost user files are not.
|
|
377
413
|
| `S3_ENDPOINT` | `string` (URL) | No | — | Custom S3 endpoint (MinIO, R2) |
|
|
378
414
|
| `S3_FORCE_PATH_STYLE` | `optionalBoolString` | No | `undefined` → `false` | Use path-style S3 URLs (required for MinIO) |
|
|
379
415
|
| `GCS_BUCKET` | `string` | If GCS | — | Google Cloud Storage bucket name |
|
|
@@ -50,8 +50,8 @@ import {
|
|
|
50
50
|
Avatar, SearchBar, ErrorBoundary,
|
|
51
51
|
// Utilities
|
|
52
52
|
cls, defaultBorderMixin, cardMixin, cardClickableMixin, paperMixin,
|
|
53
|
-
// Icons (all from @rebasepro/ui)
|
|
54
|
-
|
|
53
|
+
// Icons (all from @rebasepro/ui, under their lucide names)
|
|
54
|
+
PlusIcon, Trash2Icon
|
|
55
55
|
} from "@rebasepro/ui";
|
|
56
56
|
```
|
|
57
57
|
|
|
@@ -202,7 +202,7 @@ The typography scale is compact and uses semibold weights for headings — desig
|
|
|
202
202
|
|
|
203
203
|
### Typography Anti-Patterns
|
|
204
204
|
|
|
205
|
-
```tsx
|
|
205
|
+
```tsx no-verify
|
|
206
206
|
// ❌ NEVER: Gradient text on headings
|
|
207
207
|
<Typography className="bg-gradient-to-r from-violet-600 to-cyan-500 bg-clip-text text-transparent">
|
|
208
208
|
|
|
@@ -270,7 +270,7 @@ The `Card` component from `@rebasepro/ui` automatically applies `cardMixin` and,
|
|
|
270
270
|
// cardClickableMixin = "hover:bg-surface-50 dark:hover:bg-surface-800 cursor-pointer transition-colors duration-150"
|
|
271
271
|
```
|
|
272
272
|
|
|
273
|
-
```tsx
|
|
273
|
+
```tsx no-verify
|
|
274
274
|
// ✅ CORRECT: Using Card component
|
|
275
275
|
<Card className="p-4">
|
|
276
276
|
<Typography variant="subtitle1">Title</Typography>
|
|
@@ -407,7 +407,7 @@ The `Button` component accepts these variants and colors. Buttons use `rounded-l
|
|
|
407
407
|
|
|
408
408
|
### Button Anti-Patterns
|
|
409
409
|
|
|
410
|
-
```tsx
|
|
410
|
+
```tsx no-verify
|
|
411
411
|
// ❌ NEVER: Casting variant to bypass types
|
|
412
412
|
<Button variant={"standard" as any}>
|
|
413
413
|
|
|
@@ -432,7 +432,7 @@ The `Button` component accepts these variants and colors. Buttons use `rounded-l
|
|
|
432
432
|
|
|
433
433
|
### Use the `Alert` Component
|
|
434
434
|
|
|
435
|
-
```tsx
|
|
435
|
+
```tsx no-verify
|
|
436
436
|
// ✅ CORRECT
|
|
437
437
|
<Alert color="success" size="small">Lead captured successfully!</Alert>
|
|
438
438
|
<Alert color="error">Could not load dashboard statistics.</Alert>
|
|
@@ -508,7 +508,7 @@ When displaying numeric KPIs, use plain muted icons (matching NavigationCard)
|
|
|
508
508
|
|
|
509
509
|
### Metric Card Anti-Patterns
|
|
510
510
|
|
|
511
|
-
```tsx
|
|
511
|
+
```tsx no-verify
|
|
512
512
|
// ❌ NEVER: Gradient icon backgrounds
|
|
513
513
|
<div className="p-4 bg-gradient-to-br from-blue-500 to-indigo-600 text-white rounded-2xl shadow-lg shadow-blue-500/20">
|
|
514
514
|
|
|
@@ -555,7 +555,7 @@ For larger content sections (like a table, form, or embedded panel), use `Paper`
|
|
|
555
555
|
|
|
556
556
|
### Panel Anti-Patterns
|
|
557
557
|
|
|
558
|
-
```tsx
|
|
558
|
+
```tsx no-verify
|
|
559
559
|
// ❌ WRONG: Custom panel styling
|
|
560
560
|
<div className="p-6 rounded-2xl border bg-white dark:bg-surface-900/50 backdrop-blur-md shadow-sm">
|
|
561
561
|
|
|
@@ -217,7 +217,7 @@ import {
|
|
|
217
217
|
|
|
218
218
|
#### getPasswordResetTemplate
|
|
219
219
|
|
|
220
|
-
```typescript
|
|
220
|
+
```typescript no-verify
|
|
221
221
|
function getPasswordResetTemplate(
|
|
222
222
|
resetUrl: string,
|
|
223
223
|
user: { email: string; displayName?: string | null },
|
|
@@ -229,7 +229,7 @@ The template includes a "Reset Password" button linking to `resetUrl`, a plain-t
|
|
|
229
229
|
|
|
230
230
|
#### getEmailVerificationTemplate
|
|
231
231
|
|
|
232
|
-
```typescript
|
|
232
|
+
```typescript no-verify
|
|
233
233
|
function getEmailVerificationTemplate(
|
|
234
234
|
verifyUrl: string,
|
|
235
235
|
user: { email: string; displayName?: string | null },
|
|
@@ -241,7 +241,7 @@ The template includes a "Verify Email Address" button linking to `verifyUrl`.
|
|
|
241
241
|
|
|
242
242
|
#### getUserInvitationTemplate
|
|
243
243
|
|
|
244
|
-
```typescript
|
|
244
|
+
```typescript no-verify
|
|
245
245
|
function getUserInvitationTemplate(
|
|
246
246
|
setPasswordUrl: string,
|
|
247
247
|
user: { email: string; displayName?: string | null },
|
|
@@ -253,7 +253,7 @@ Sent when an admin creates a user via `POST /api/admin/users` without specifying
|
|
|
253
253
|
|
|
254
254
|
#### getWelcomeEmailTemplate
|
|
255
255
|
|
|
256
|
-
```typescript
|
|
256
|
+
```typescript no-verify
|
|
257
257
|
function getWelcomeEmailTemplate(
|
|
258
258
|
user: { email: string; displayName?: string | null },
|
|
259
259
|
appName?: string, // default: "Rebase"
|