@rebasepro/cli 0.6.1 → 0.7.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.
Files changed (49) hide show
  1. package/README.md +0 -1
  2. package/dist/commands/api-keys.d.ts +1 -0
  3. package/dist/commands/generate_sdk.d.ts +1 -1
  4. package/dist/index.es.js +399 -57
  5. package/dist/index.es.js.map +1 -1
  6. package/package.json +20 -15
  7. package/templates/template/backend/package.json +0 -1
  8. package/templates/template/backend/src/index.ts +1 -1
  9. package/templates/template/config/collections/posts.ts +1 -3
  10. package/templates/template/config/collections/tags.ts +1 -3
  11. package/templates/template/config/collections/users.ts +1 -4
  12. package/templates/template/package.json +0 -1
  13. package/templates/template/scripts/example.ts +4 -1
  14. package/skills/rebase-admin/SKILL.md +0 -710
  15. package/skills/rebase-api/SKILL.md +0 -662
  16. package/skills/rebase-api/references/.gitkeep +0 -3
  17. package/skills/rebase-auth/SKILL.md +0 -1143
  18. package/skills/rebase-auth/references/.gitkeep +0 -3
  19. package/skills/rebase-backend-postgres/SKILL.md +0 -633
  20. package/skills/rebase-backend-postgres/references/.gitkeep +0 -3
  21. package/skills/rebase-basics/SKILL.md +0 -749
  22. package/skills/rebase-basics/references/.gitkeep +0 -3
  23. package/skills/rebase-collections/SKILL.md +0 -1328
  24. package/skills/rebase-collections/references/.gitkeep +0 -3
  25. package/skills/rebase-cron-jobs/SKILL.md +0 -699
  26. package/skills/rebase-cron-jobs/references/.gitkeep +0 -1
  27. package/skills/rebase-custom-functions/SKILL.md +0 -233
  28. package/skills/rebase-deployment/SKILL.md +0 -885
  29. package/skills/rebase-deployment/references/.gitkeep +0 -3
  30. package/skills/rebase-design-language/SKILL.md +0 -692
  31. package/skills/rebase-email/SKILL.md +0 -701
  32. package/skills/rebase-email/references/.gitkeep +0 -1
  33. package/skills/rebase-entity-history/SKILL.md +0 -485
  34. package/skills/rebase-entity-history/references/.gitkeep +0 -1
  35. package/skills/rebase-local-env-setup/SKILL.md +0 -189
  36. package/skills/rebase-local-env-setup/references/.gitkeep +0 -3
  37. package/skills/rebase-realtime/SKILL.md +0 -755
  38. package/skills/rebase-realtime/references/.gitkeep +0 -3
  39. package/skills/rebase-sdk/SKILL.md +0 -594
  40. package/skills/rebase-sdk/references/.gitkeep +0 -0
  41. package/skills/rebase-storage/SKILL.md +0 -765
  42. package/skills/rebase-storage/references/.gitkeep +0 -3
  43. package/skills/rebase-studio/SKILL.md +0 -746
  44. package/skills/rebase-studio/references/.gitkeep +0 -3
  45. package/skills/rebase-ui-components/SKILL.md +0 -1488
  46. package/skills/rebase-ui-components/references/.gitkeep +0 -3
  47. package/skills/rebase-webhooks/SKILL.md +0 -623
  48. package/skills/rebase-webhooks/references/.gitkeep +0 -1
  49. package/templates/template/backend/drizzle.config.ts +0 -51
@@ -1,3 +0,0 @@
1
- # References
2
-
3
- Reference documentation for the rebase-auth skill. Add detailed reference files here.
@@ -1,633 +0,0 @@
1
- ---
2
- name: rebase-backend-postgres
3
- description: Guide for setting up and managing the Rebase PostgreSQL backend with Drizzle ORM. Use this skill when the user needs help with database setup, schema generation, migrations, connection pooling, read replicas, direct connections, Drizzle configuration, or backend initialization.
4
- ---
5
-
6
- # Rebase PostgreSQL Backend
7
-
8
- > **WARNING FOR AGENTS**: If you are writing a script or performing data tasks (e.g., seeding, migrating content), **default to using the Rebase SDK** (`@rebasepro/client` or `@rebasepro/server-core`). **NEVER** use `psql` or raw SQL to manipulate data directly unless specifically instructed to do so for low-level debugging. Bypassing the SDK circumvents schema validation, access controls, and lifecycle hooks.
9
-
10
- Rebase uses PostgreSQL as its primary database, with Drizzle ORM for type-safe schema management and migrations.
11
-
12
- ## Architecture
13
-
14
- ```
15
- Collections (TypeScript) → Drizzle Schema (generated) → PostgreSQL (database)
16
- ```
17
-
18
- The backend uses a **two-step process**:
19
- 1. **`rebase schema generate`** reads your Rebase collection definitions and generates a Drizzle ORM schema file (`schema.generated.ts`)
20
- 2. **`rebase db push`** or **`rebase db generate` + `rebase db migrate`** applies the schema to the database
21
-
22
- ## Prerequisites
23
-
24
- - PostgreSQL 14+ (local or Docker)
25
- - `DATABASE_URL` environment variable set in the project root's `.env` file
26
- - pnpm installed
27
-
28
- ## Quick Start (Development)
29
-
30
- ```bash
31
- # From the project root directory:
32
-
33
- # 1. Generate Drizzle schema from collections
34
- rebase schema generate
35
-
36
- # 2. Push changes directly to database (no migration files)
37
- rebase db push
38
- ```
39
-
40
- ## Production Workflow (With Migrations)
41
-
42
- ```bash
43
- # 1. Generate Drizzle schema
44
- rebase schema generate
45
-
46
- # 2. Generate SQL migration files (creates timestamped .sql in ./drizzle/)
47
- rebase db generate
48
-
49
- # 3. Review the generated SQL before applying!
50
-
51
- # 4. Apply migrations
52
- rebase db migrate
53
- ```
54
-
55
- ## Command Reference
56
-
57
- | Command | Description | When to Use |
58
- |---------|-------------|-------------|
59
- | `rebase schema generate` | Collections → Drizzle schema | Always first step |
60
- | `rebase schema introspect` | DB → Rebase collections | Legacy DB import (Preferred) |
61
- | `rebase db push` | Apply schema directly to DB | Development |
62
- | `rebase db generate` | Generate schema + create SQL migration files | Production prep |
63
- | `rebase db migrate` | Run pending migrations | Production deploy |
64
- | `rebase db studio` | Visual database browser — Drizzle Studio | Debugging |
65
-
66
- ## Key Backend Packages
67
-
68
- | Package | Purpose |
69
- |---------|---------|
70
- | `@rebasepro/server-core` | Hono server coordinator, API generation, auth, storage, env validation |
71
- | `@rebasepro/server-postgresql` | PostgreSQL bootstrapper, data driver, connection helpers, realtime (LISTEN/NOTIFY) |
72
- | `@rebasepro/types` | Shared TypeScript type definitions (`PostgresCollection`, etc.) |
73
-
74
- ## Connection Functions
75
-
76
- All connection functions are exported from `@rebasepro/server-postgresql`.
77
-
78
- ### createPostgresDatabaseConnection()
79
-
80
- The primary connection factory. Creates a Drizzle-backed Postgres connection with a production-grade pool.
81
-
82
- ```typescript
83
- import { createPostgresDatabaseConnection } from "@rebasepro/server-postgresql";
84
-
85
- const { db, pool, connectionString } = createPostgresDatabaseConnection(
86
- process.env.DATABASE_URL!,
87
- undefined, // optional: Drizzle schema for relational API
88
- { // optional: PostgresPoolConfig overrides
89
- max: 20,
90
- idleTimeoutMillis: 30_000,
91
- connectionTimeoutMillis: 10_000,
92
- }
93
- );
94
- ```
95
-
96
- **Signature:**
97
-
98
- ```typescript
99
- function createPostgresDatabaseConnection(
100
- connectionString: string,
101
- schema?: Record<string, unknown>,
102
- poolConfig?: PostgresPoolConfig
103
- ): { db: NodePgDatabase; pool: Pool; connectionString: string }
104
- ```
105
-
106
- **Returns** `{ db, pool, connectionString }` — the `pool` is exposed so callers can register shutdown hooks (`pool.end()`) or monitor pool metrics.
107
-
108
- ### createReadReplicaConnection()
109
-
110
- Creates a connection to a read replica for distributing read queries. Uses a default pool max of **10**.
111
-
112
- ```typescript
113
- import { createReadReplicaConnection } from "@rebasepro/server-postgresql";
114
-
115
- const readResources = createReadReplicaConnection(
116
- process.env.DATABASE_READ_URL!,
117
- mergedSchema // optional: same Drizzle schema for relational API
118
- );
119
- ```
120
-
121
- **Signature:**
122
-
123
- ```typescript
124
- function createReadReplicaConnection(
125
- connectionString: string,
126
- schema?: Record<string, unknown>,
127
- poolConfig?: PostgresPoolConfig
128
- ): { db: NodePgDatabase; pool: Pool; connectionString: string }
129
- ```
130
-
131
- > **IMPORTANT FOR AGENTS:** The bootstrapper automatically creates a read replica connection when `DATABASE_READ_URL` is set and differs from the primary `connectionString`. You do NOT need to call this manually in most cases.
132
-
133
- ### createDirectDatabaseConnection()
134
-
135
- Creates a direct (non-pooled) connection for session-level features incompatible with PgBouncer transaction mode: **LISTEN/NOTIFY**, prepared statements, advisory locks. Uses a smaller default pool max of **5**.
136
-
137
- ```typescript
138
- import { createDirectDatabaseConnection } from "@rebasepro/server-postgresql";
139
-
140
- const directResources = createDirectDatabaseConnection(
141
- process.env.DATABASE_DIRECT_URL!,
142
- mergedSchema
143
- );
144
- ```
145
-
146
- **Signature:**
147
-
148
- ```typescript
149
- function createDirectDatabaseConnection(
150
- connectionString: string,
151
- schema?: Record<string, unknown>,
152
- poolConfig?: PostgresPoolConfig
153
- ): { db: NodePgDatabase; pool: Pool; connectionString: string }
154
- ```
155
-
156
- > **IMPORTANT FOR AGENTS:** The bootstrapper uses `DATABASE_DIRECT_URL` automatically to bypass PgBouncer for LISTEN/NOTIFY realtime. If `DATABASE_DIRECT_URL` is not set, it falls back to the primary `connectionString`.
157
-
158
- ### PostgresPoolConfig
159
-
160
- All three connection functions accept an optional `PostgresPoolConfig` for programmatic pool tuning:
161
-
162
- | Property | Type | Default | Description |
163
- |----------|------|---------|-------------|
164
- | `max` | `number` | `20` (`10` for replica, `5` for direct) | Maximum connections in the pool |
165
- | `idleTimeoutMillis` | `number` | `30000` | Close idle connections after this many ms |
166
- | `connectionTimeoutMillis` | `number` | `10000` | Abort connection attempts after this many ms |
167
- | `queryTimeout` | `number` | `30000` | Per-query timeout in ms |
168
- | `statementTimeout` | `number` | `30000` | Per-statement timeout in ms |
169
- | `keepAlive` | `boolean` | `true` | Enable TCP keep-alive |
170
-
171
- ## DatabasePoolManager
172
-
173
- The `DatabasePoolManager` manages multiple database connection pools dynamically. Used internally by the bootstrapper when `adminConnectionString` is configured — enables cross-database operations like branching and SQL execution against arbitrary databases.
174
-
175
- ```typescript
176
- import { DatabasePoolManager } from "@rebasepro/server-postgresql";
177
-
178
- const poolManager = new DatabasePoolManager(process.env.ADMIN_CONNECTION_STRING!);
179
-
180
- // Get a Drizzle instance for a specific database (creates pool on demand)
181
- const db = poolManager.getDrizzle("my_branch_db");
182
-
183
- // Get the raw pg.Pool
184
- const pool = poolManager.getPool("my_branch_db");
185
-
186
- // Check if a pool exists
187
- poolManager.hasPool("my_branch_db"); // boolean
188
-
189
- // Disconnect a specific database (required before DROP DATABASE / CREATE DATABASE ... TEMPLATE)
190
- await poolManager.disconnectDatabase("my_branch_db");
191
-
192
- // Shut down all pools
193
- await poolManager.shutdown();
194
- ```
195
-
196
- **API Reference:**
197
-
198
- | Method | Signature | Description |
199
- |--------|-----------|-------------|
200
- | `constructor` | `new DatabasePoolManager(adminConnectionString: string)` | Parses the database name from the URL |
201
- | `getDrizzle` | `(databaseName: string) => NodePgDatabase` | Returns (or creates) a Drizzle instance for the database |
202
- | `getPool` | `(databaseName: string) => Pool` | Returns (or creates) a raw pg Pool for the database |
203
- | `hasPool` | `(databaseName: string) => boolean` | Checks if a pool exists |
204
- | `disconnectDatabase` | `(databaseName: string) => Promise<void>` | Ends pool + removes cached instances |
205
- | `shutdown` | `() => Promise<void>` | Ends all pools |
206
- | `defaultDatabaseName` | `string` (readonly) | The database name extracted from the admin connection string |
207
-
208
- **Dynamic pool settings:** Each dynamically created pool uses `max: 10`, `idleTimeoutMillis: 10000`, `allowExitOnIdle: true`.
209
-
210
- ## Backend Initialization
211
-
212
- Rebase supports two equivalent APIs for configuring the PostgreSQL backend:
213
-
214
- ### Option A: createPostgresAdapter() (Recommended)
215
-
216
- The `DatabaseAdapter` API — a simpler, flattened interface that wraps the bootstrapper internally:
217
-
218
- ```typescript
219
- import { Hono } from "hono";
220
- import { getRequestListener } from "@hono/node-server";
221
- import { createServer } from "http";
222
- import path from "path";
223
- import {
224
- initializeRebaseBackend,
225
- HonoEnv
226
- } from "@rebasepro/server-core";
227
- import { createPostgresDatabaseConnection, createPostgresAdapter } from "@rebasepro/server-postgresql";
228
- import { tables, enums, relations } from "./schema.generated.js";
229
-
230
- const app = new Hono<HonoEnv>();
231
- const server = createServer(getRequestListener(app.fetch));
232
-
233
- const { db, pool, connectionString } = createPostgresDatabaseConnection(
234
- process.env.DATABASE_URL!,
235
- undefined,
236
- { max: env.DB_POOL_MAX }
237
- );
238
-
239
- const backend = await initializeRebaseBackend({
240
- collectionsDir: path.resolve(__dirname, "../../config/collections"),
241
- functionsDir: path.resolve(__dirname, "../functions"),
242
- cronsDir: path.resolve(__dirname, "../crons"),
243
- server,
244
- app,
245
- database: createPostgresAdapter({
246
- connection: db,
247
- schema: { tables, enums, relations },
248
- adminConnectionString: process.env.ADMIN_CONNECTION_STRING || process.env.DATABASE_URL,
249
- connectionString // enables cross-instance realtime via LISTEN/NOTIFY
250
- }),
251
- auth: {
252
- jwtSecret: process.env.JWT_SECRET!,
253
- accessExpiresIn: "1h",
254
- refreshExpiresIn: "30d",
255
- allowRegistration: false,
256
- },
257
- storage: { type: "local", basePath: "./uploads" },
258
- history: true,
259
- });
260
-
261
- server.listen(3001);
262
- ```
263
-
264
- ### Option B: createPostgresBootstrapper()
265
-
266
- The bootstrapper protocol — database-specific logic is encapsulated in bootstrapper objects:
267
-
268
- ```typescript
269
- import { createPostgresDatabaseConnection, createPostgresBootstrapper } from "@rebasepro/server-postgresql";
270
- import { tables, enums, relations } from "./schema.generated.js";
271
-
272
- const { db, pool, connectionString } = createPostgresDatabaseConnection(process.env.DATABASE_URL!);
273
-
274
- const backend = await initializeRebaseBackend({
275
- // ...same config as above...
276
- bootstrappers: [
277
- createPostgresBootstrapper({
278
- connection: db,
279
- schema: { tables, enums, relations },
280
- adminConnectionString: process.env.DATABASE_URL,
281
- connectionString
282
- })
283
- ],
284
- // ...auth, storage, etc...
285
- });
286
- ```
287
-
288
- > **IMPORTANT FOR AGENTS:** When both `database` and `bootstrappers` are provided, `database` takes precedence and `bootstrappers` is ignored.
289
-
290
- ### PostgresDriverConfig Properties
291
-
292
- The config object passed to both `createPostgresAdapter()` and `createPostgresBootstrapper()`:
293
-
294
- | Property | Type | Required | Description |
295
- |----------|------|----------|-------------|
296
- | `connection` | `NodePgDatabase` | ✅ | Drizzle database connection from `createPostgresDatabaseConnection()` |
297
- | `schema` | `{ tables, enums, relations }` | ✅ | Generated Drizzle schema objects |
298
- | `connectionString` | `string` | No | Primary connection URL — enables LISTEN/NOTIFY realtime |
299
- | `adminConnectionString` | `string` | No | Admin-level connection — enables `DatabasePoolManager`, SQL editor, branching |
300
-
301
- ### Bootstrapper Lifecycle
302
-
303
- The `initializeRebaseBackend()` coordinator calls bootstrapper methods in this order:
304
-
305
- | Step | Method | Purpose |
306
- |------|--------|---------|
307
- | 1 | `initializeDriver(config)` | Creates the `PostgresBackendDriver`, registry, realtime service, read replica, pool manager |
308
- | 2 | `initializeAuth(config, driverResult)` | Creates auth tables, `UserService`, `PostgresAuthRepository` |
309
- | 3 | `initializeHistory(config, driverResult)` | Creates the `rebase.entity_history` table and `HistoryService` |
310
- | 4 | `initializeRealtime(config, driverResult)` | Returns the `RealtimeService` for WebSocket subscriptions |
311
- | 5 | `initializeWebsockets(server, ...)` | Creates the WebSocket upgrade handler for realtime |
312
- | 6 | `getAdmin(driverResult)` | Returns the `DatabaseAdmin` for SQL execution |
313
- | 7 | `mountRoutes(app, basePath, driverResult)` | Hook for driver-specific routes (currently a no-op for Postgres) |
314
-
315
- ### RebaseBackendConfig Options
316
-
317
- | Property | Type | Default | Description |
318
- |----------|------|---------|-------------|
319
- | `collectionsDir` | `string` | — | Auto-discover collection definition files |
320
- | `functionsDir` | `string` | — | Auto-discover custom Hono route files |
321
- | `cronsDir` | `string` | — | Auto-discover cron job files |
322
- | `server` | `http.Server` | — | Node.js HTTP server instance |
323
- | `app` | `Hono<HonoEnv>` | — | Hono application instance |
324
- | `basePath` | `string` | `"/api"` | Base path for all API routes |
325
- | `database` | `DatabaseAdapter` | — | Adapter API (takes precedence over `bootstrappers`) |
326
- | `bootstrappers` | `BackendBootstrapper[]` | — | Bootstrapper protocol (alternative to `database`) |
327
- | `auth` | `RebaseAuthConfig \| AuthAdapter` | — | Authentication configuration or external adapter |
328
- | `storage` | `BackendStorageConfig \| StorageController \| Record<string, ...>` | — | File storage configuration |
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
- | `enableSwagger` | `boolean` | `true` | Enable OpenAPI/Swagger documentation |
332
- | `cronPersistence` | `boolean` | `true` | Persist cron execution logs to database |
333
- | `maxBodySize` | `number` | `10485760` (10MB) | Max request body size in bytes. `0` to disable |
334
- | `csrf` | `{ origin: string \| string[] \| (origin: string) => boolean }` | — | CSRF protection (opt-in, disabled by default) |
335
- | `hooks` | `BackendHooks` | — | Backend-level hooks for intercepting admin data |
336
- | `logging` | `{ level?: "error" \| "warn" \| "info" \| "debug" }` | — | Log level configuration |
337
-
338
- ### Auth Configuration (RebaseAuthConfig)
339
-
340
- | Property | Type | Default | Description |
341
- |----------|------|---------|-------------|
342
- | `collection` | `EntityCollection` | Built-in `rebase.users` | Auth users collection |
343
- | `jwtSecret` | `string` | Auto-generated in dev | JWT signing secret (≥32 chars) |
344
- | `accessExpiresIn` | `string` | `"1h"` | Access token TTL |
345
- | `refreshExpiresIn` | `string` | `"30d"` | Refresh token TTL |
346
- | `requireAuth` | `boolean` | `true` | Require auth for data routes |
347
- | `allowRegistration` | `boolean` | **`false`** | Enable user self-registration |
348
- | `serviceKey` | `string` | `REBASE_SERVICE_KEY` env | Service-to-service auth key (≥32 chars) |
349
- | `defaultRole` | `string` | — | Default role for new users |
350
- | `seedDefaultRoles` | `boolean` | — | Seed default roles on startup |
351
- | `email` | `EmailConfig` | — | SMTP email configuration |
352
- | `google` | `{ clientId, clientSecret? }` | — | Google OAuth |
353
- | `github` | `{ clientId, clientSecret }` | — | GitHub OAuth |
354
- | `microsoft` | `{ clientId, clientSecret, tenantId? }` | — | Microsoft OAuth |
355
- | `apple` | `{ clientId, teamId, keyId, privateKey }` | — | Apple OAuth |
356
- | `facebook` | `{ clientId, clientSecret }` | — | Facebook OAuth |
357
- | `twitter` | `{ clientId, clientSecret }` | — | Twitter OAuth |
358
- | `discord` | `{ clientId, clientSecret }` | — | Discord OAuth |
359
- | `linkedin` | `{ clientId, clientSecret }` | — | LinkedIn OAuth |
360
- | `gitlab` | `{ clientId, clientSecret, baseUrl? }` | — | GitLab OAuth |
361
- | `bitbucket` | `{ clientId, clientSecret }` | — | Bitbucket OAuth |
362
- | `slack` | `{ clientId, clientSecret }` | — | Slack OAuth |
363
- | `spotify` | `{ clientId, clientSecret }` | — | Spotify OAuth |
364
- | `hooks` | `AuthHooks` | — | Override password hashing, validation, etc. |
365
-
366
- > **WARNING FOR AGENTS:** `allowRegistration` defaults to **`false`**, not `true`. You must explicitly set it to `true` if you want users to register themselves.
367
-
368
- ## Environment Variables
369
-
370
- ### loadEnv()
371
-
372
- The `loadEnv()` function validates all environment variables at startup using Zod. It is exported from `@rebasepro/server-core`.
373
-
374
- **Basic usage:**
375
-
376
- ```typescript
377
- import dotenv from "dotenv";
378
- import { loadEnv } from "@rebasepro/server-core";
379
-
380
- dotenv.config({ path: "../../.env" });
381
-
382
- export const env = loadEnv();
383
- ```
384
-
385
- **Extended — add your own typed variables:**
386
-
387
- ```typescript
388
- import dotenv from "dotenv";
389
- import { loadEnv, z } from "@rebasepro/server-core";
390
-
391
- dotenv.config({ path: "../../.env" });
392
-
393
- export const env = loadEnv({
394
- extend: z.object({
395
- SMTP_HOST: z.string().optional(),
396
- SMTP_PORT: z.string().default("587").transform(Number),
397
- STRIPE_SECRET_KEY: z.string(),
398
- })
399
- });
400
- // env.SMTP_HOST → string | undefined (fully typed)
401
- // env.STRIPE_SECRET_KEY → string (validated, required)
402
- ```
403
-
404
- **Signatures:**
405
-
406
- ```typescript
407
- function loadEnv(): RebaseEnv;
408
- function loadEnv<E extends z.AnyZodObject>(options: { extend: E }): RebaseEnv & z.infer<E>;
409
- ```
410
-
411
- ### Dev-Mode Auto-Generated Secrets
412
-
413
- In non-production mode, `loadEnv()` **auto-generates** ephemeral `JWT_SECRET` and `REBASE_SERVICE_KEY` if not set, so developers can start without manual setup. These are regenerated on every restart — existing tokens will be invalidated.
414
-
415
- ### Full Environment Variable Reference
416
-
417
- | Variable | Required | Default | Description |
418
- |----------|----------|---------|-------------|
419
- | `DATABASE_URL` | ✅ Yes | — | PostgreSQL connection string (must be a valid URL) |
420
- | `JWT_SECRET` | ✅ Yes (≥32 chars) | Auto-generated in dev | JWT signing secret |
421
- | `NODE_ENV` | No | `development` | `development`, `production`, or `test` |
422
- | `PORT` | No | `3001` | Server port |
423
- | `ADMIN_CONNECTION_STRING` | No | — | Admin-level DB connection for cross-database operations |
424
- | `DATABASE_DIRECT_URL` | No | — | Direct connection URL bypassing PgBouncer (for LISTEN/NOTIFY) |
425
- | `DATABASE_READ_URL` | No | — | Read replica connection URL |
426
- | `DB_POOL_MAX` | No | `20` | Max DB connection pool size |
427
- | `DB_POOL_IDLE_TIMEOUT` | No | `30000` | Pool idle timeout (ms) |
428
- | `DB_POOL_CONNECT_TIMEOUT` | No | `10000` | Pool connect timeout (ms) |
429
- | `DISABLE_DB_ROLE_SWITCHING` | No | — | Set to `true` to skip PostgreSQL role switching |
430
- | `JWT_ACCESS_EXPIRES_IN` | No | `1h` | Access token TTL |
431
- | `JWT_REFRESH_EXPIRES_IN` | No | `30d` | Refresh token TTL |
432
- | `GOOGLE_CLIENT_ID` | No | — | Google OAuth client ID |
433
- | `GOOGLE_CLIENT_SECRET` | No | — | Google OAuth client secret |
434
- | `REBASE_SERVICE_KEY` | No | Auto-generated in dev | Service-to-service auth key |
435
- | `ALLOW_REGISTRATION` | No | `false` | Enable user self-registration |
436
- | `ALLOW_LOCALHOST_IN_PRODUCTION` | No | `false` | Allow localhost URLs in production env vars |
437
- | `CORS_ORIGINS` | No (⚠ required in prod) | — | Comma-separated allowed origins |
438
- | `FRONTEND_URL` | No | — | Frontend URL (used for CORS) |
439
- | `STORAGE_TYPE` | No | `local` | `local` or `s3` |
440
- | `STORAGE_PATH` | No | — | Path for local file storage |
441
- | `FORCE_LOCAL_STORAGE` | No | `false` | Suppress local storage warning in production |
442
- | `S3_BUCKET` | No (if s3) | — | S3 bucket name |
443
- | `S3_REGION` | No | — | S3 region |
444
- | `S3_ACCESS_KEY_ID` | No (if s3) | — | S3 access key |
445
- | `S3_SECRET_ACCESS_KEY` | No (if s3) | — | S3 secret key |
446
- | `S3_ENDPOINT` | No | — | S3 endpoint URL (MinIO, R2) |
447
- | `S3_FORCE_PATH_STYLE` | No | `false` | Force path-style S3 URLs |
448
-
449
- ### Production Validations
450
-
451
- `loadEnv()` enforces strict validation in production (`NODE_ENV=production`):
452
-
453
- | Rule | Error Message |
454
- |------|---------------|
455
- | `CORS_ORIGINS` or `FRONTEND_URL` required | `CORS_ORIGINS or FRONTEND_URL must be set in production to secure the API.` |
456
- | Auto-generated secrets blocked | `JWT_SECRET, REBASE_SERVICE_KEY must be explicitly set in production.` |
457
- | No localhost URLs (unless `ALLOW_LOCALHOST_IN_PRODUCTION=true`) | `Environment variable DATABASE_URL contains a local/loopback URL...` |
458
-
459
- ## Drizzle Configuration
460
-
461
- The `drizzle.config.ts` in `app/backend/` is configured to:
462
- - **Only manage tables defined in your schema** — other tables (like internal `rebase.*` tables) are ignored via `tablesFilter`
463
- - **Auto-detect schemas** from collection definitions (e.g., `public`, custom schemas)
464
- - Use the `DATABASE_URL` from your `.env` file
465
- - Output migrations to `./drizzle/`
466
- - Ignore PostgreSQL roles (`entities.roles: false`)
467
- - Filter PostGIS extension tables
468
-
469
- ```typescript
470
- import "dotenv/config";
471
- import { defineConfig } from "drizzle-kit";
472
- import { tables } from "./src/schema.generated";
473
- import { getTableName } from "drizzle-orm";
474
- import { getTableConfig, PgTable } from "drizzle-orm/pg-core";
475
-
476
- const tableNames = Object.values(tables)
477
- .filter(table => getTableConfig(table as PgTable).schema !== "rebase")
478
- .map(table => getTableName(table as PgTable));
479
-
480
- const schemas = Array.from(new Set(
481
- Object.values(tables)
482
- .map(table => getTableConfig(table as PgTable).schema || "public")
483
- .filter(schema => schema !== "rebase")
484
- ));
485
-
486
- export default defineConfig({
487
- schema: "./src/schema.generated.ts",
488
- out: "./drizzle",
489
- dialect: "postgresql",
490
- dbCredentials: { url: process.env.DATABASE_URL! },
491
- tablesFilter: tableNames,
492
- schemaFilter: schemas.length > 0 ? schemas : ["public"],
493
- entities: { roles: false },
494
- extensionsFilters: ["postgis"]
495
- });
496
- ```
497
-
498
- ## Entity History (Audit Trail)
499
-
500
- Enable audit logging for all entity mutations by setting `history: true` in the backend config:
501
-
502
- ```typescript
503
- const backend = await initializeRebaseBackend({
504
- // ...
505
- history: true, // Enable with default settings
506
- });
507
-
508
- // Or with a retention policy:
509
- const backend = await initializeRebaseBackend({
510
- // ...
511
- history: { retention: 90 }, // Auto-purge entries older than 90 days
512
- });
513
- ```
514
-
515
- When enabled:
516
- - The bootstrapper auto-creates a `rebase.entity_history` table
517
- - Every `INSERT`, `UPDATE`, `DELETE` is recorded with before/after snapshots
518
- - History is queryable via the `/:slug/:entityId/history` REST endpoint
519
-
520
- ## Health Check
521
-
522
- The backend exposes a `/health` endpoint that returns:
523
-
524
- ```json
525
- {
526
- "status": "ok",
527
- "latencyMs": 2.5,
528
- "details": { ... }
529
- }
530
- ```
531
-
532
- HTTP 200 for healthy, 503 for degraded. Internally executes `SELECT 1` to verify database connectivity.
533
-
534
- ## Graceful Shutdown
535
-
536
- The recommended graceful shutdown pattern:
537
-
538
- ```typescript
539
- let isShuttingDown = false;
540
- const gracefulShutdown = async (signal: string) => {
541
- if (isShuttingDown) return;
542
- isShuttingDown = true;
543
-
544
- const forceTimer = setTimeout(() => {
545
- console.error("Shutdown timed out after 15s. Force-exiting.");
546
- process.exit(1);
547
- }, 15000);
548
- forceTimer.unref();
549
-
550
- try {
551
- // 1. backend.shutdown() stops cron, destroys realtime, closes HTTP server
552
- await backend.shutdown();
553
-
554
- // 2. Drain the database connection pool
555
- await pool.end();
556
-
557
- clearTimeout(forceTimer);
558
- process.exit(0);
559
- } catch (err) {
560
- console.error("Error during shutdown:", err);
561
- process.exit(1);
562
- }
563
- };
564
-
565
- process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
566
- process.on("SIGINT", () => gracefulShutdown("SIGINT"));
567
- ```
568
-
569
- ### backend.shutdown() Sequence
570
-
571
- The `shutdown(timeoutMs?: number)` method (default timeout: 15000ms) performs:
572
-
573
- 1. **Stop cron scheduler** — prevents new cron executions
574
- 2. **Destroy realtime services** — tears down LISTEN clients, debounce timers, subscriptions (must happen before pool.end())
575
- 3. **Close HTTP server** — stops accepting new connections, drains in-flight requests
576
- 4. **Force-resolve** after timeout (pass `0` to disable, useful in tests)
577
-
578
- > **WARNING FOR AGENTS:** Call `backend.shutdown()` FIRST, then `pool.end()`. Do NOT call `server.close()` separately — `backend.shutdown()` already closes the HTTP server internally. Calling both will deadlock.
579
-
580
- ## JWT Dual-Package Hazard
581
-
582
- > [!WARNING]
583
- > **JWT Dual-Package Hazard (Monorepos / pnpm)**
584
- > When running a backend inside a monorepo workspace (especially with `tsx` and `--preserve-symlinks`), you may encounter a `RebaseApiError: JWT secret not configured. Call configureJwt() first` error. This occurs because Node.js resolves two different module instances of `@rebasepro/server-core`.
585
- >
586
- > **Fix:** Explicitly call `configureJwt` in your backend's entry point **before** `initializeRebaseBackend`:
587
- > ```typescript
588
- > import { initializeRebaseBackend, configureJwt } from "@rebasepro/server-core";
589
- >
590
- > configureJwt({
591
- > secret: process.env.JWT_SECRET!,
592
- > accessExpiresIn: "1h",
593
- > refreshExpiresIn: "30d"
594
- > });
595
- >
596
- > const backend = await initializeRebaseBackend({ ... });
597
- > ```
598
-
599
- ## Important Notes
600
-
601
- - **To import a legacy database**, use `rebase schema introspect` to generate Rebase Collections.
602
- - **Never use `schema introspect` then `db migrate`** — introspected databases already have the tables
603
- - **Always backup before production migrations** — `ALTER COLUMN` or `DROP COLUMN` can cause data loss
604
- - **Tables not in schema are ignored** — custom tables and internal Rebase tables are safe
605
- - **Review generated SQL** — always inspect the `.sql` files in `./drizzle/` before applying
606
- - **Collections directory** — Collection definitions are defined in the `config/collections/` directory.
607
-
608
- ## Troubleshooting
609
-
610
- ### 1. SQL Editor Permission Denied (`permission denied for table <name>`)
611
- - **Symptoms:** You can view data in the collection/CMS spreadsheet view, but running custom SQL queries (like `SELECT * FROM table;`) in the Rebase Studio SQL Editor throws `cause: error: permission denied for table <table_name>`.
612
- - **Cause:** Rebase tries to switch database roles to match the active user's role (e.g., `SET LOCAL ROLE "admin"`). If you are using custom auth (roles defined only in the database `rebase.roles` table rather than actual PostgreSQL roles), or if the database-level role doesn't have `SELECT` privileges, the query fails. The CMS view does not trigger role-switching and runs under the main connection user (which is typically a superuser/owner and bypasses RLS).
613
- - **Solution:** Add `DISABLE_DB_ROLE_SWITCHING=true` to your backend `.env` configuration. This skips role switching, executing queries under the connection owner user.
614
-
615
- ### 2. SQL Editor/Studio Schema Fetch Failed (`Cross-database execution requires adminConnectionString`)
616
- - **Symptoms:** Running queries in the SQL Editor or attempting to load schemas in Studio throws `Failed to fetch schema: Cross-database execution requires adminConnectionString to be configured in the backend.`
617
- - **Cause:** The PostgreSQL bootstrapper requires `adminConnectionString` and `getAdmin()` to be configured to execute database administration commands (including schema fetch). If `adminConnectionString` is set to `undefined` or `getAdmin()` returns `undefined` (often done to enforce a zero-schema-change requirement), administrative commands fail.
618
- - **Solution:** Ensure `adminConnectionString` is passed to `createPostgresBootstrapper` or `createPostgresAdapter` (typically `adminConnectionString: env.ADMIN_CONNECTION_STRING || env.DATABASE_URL`) and ensure `getAdmin()` is not overridden to return `undefined`.
619
-
620
- ### 3. Read Replica Not Being Used
621
- - **Symptoms:** All queries go to the primary database even though `DATABASE_READ_URL` is set.
622
- - **Cause:** The bootstrapper only creates the read replica connection when `DATABASE_READ_URL` differs from the primary `connectionString`.
623
- - **Solution:** Ensure `DATABASE_READ_URL` is a different URL from `DATABASE_URL` and points to an actual read replica.
624
-
625
- ### 4. PgBouncer Breaks Realtime (LISTEN/NOTIFY)
626
- - **Symptoms:** Realtime subscriptions don't receive updates in production.
627
- - **Cause:** PgBouncer in transaction mode does not support session-level LISTEN/NOTIFY.
628
- - **Solution:** Set `DATABASE_DIRECT_URL` to a connection string that bypasses PgBouncer and connects directly to PostgreSQL.
629
-
630
- ## References
631
-
632
- - **Documentation:** [rebase.pro/docs](https://rebase.pro/docs)
633
- - **GitHub:** [github.com/rebasepro/rebase](https://github.com/rebasepro/rebase)
@@ -1,3 +0,0 @@
1
- # References
2
-
3
- Reference documentation for the rebase-backend-postgres skill. Add detailed reference files here.