@rebasepro/cli 0.0.1-canary.eae7889 → 0.0.1-canary.eb08332

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 (74) hide show
  1. package/LICENSE +17 -196
  2. package/README.md +57 -33
  3. package/dist/commands/api-keys.d.ts +1 -0
  4. package/dist/commands/build.d.ts +1 -0
  5. package/dist/commands/cloud/auth.d.ts +3 -0
  6. package/dist/commands/cloud/context.d.ts +61 -0
  7. package/dist/commands/cloud/databases.d.ts +1 -0
  8. package/dist/commands/cloud/deploy.d.ts +2 -0
  9. package/dist/commands/cloud/index.d.ts +1 -0
  10. package/dist/commands/cloud/link.d.ts +5 -0
  11. package/dist/commands/cloud/orgs.d.ts +1 -0
  12. package/dist/commands/cloud/projects.d.ts +13 -0
  13. package/dist/commands/cloud/resources.d.ts +6 -0
  14. package/dist/commands/generate_sdk.d.ts +1 -1
  15. package/dist/commands/init.d.ts +39 -0
  16. package/dist/commands/skills.d.ts +1 -0
  17. package/dist/commands/start.d.ts +1 -0
  18. package/dist/index.d.ts +6 -1
  19. package/dist/index.es.js +3785 -990
  20. package/dist/index.es.js.map +1 -1
  21. package/dist/utils/package-manager.d.ts +33 -0
  22. package/dist/utils/project.d.ts +16 -1
  23. package/package.json +26 -23
  24. package/templates/overlays/baas/README.md +37 -0
  25. package/templates/overlays/baas/backend/package.json +31 -0
  26. package/templates/overlays/baas/backend/src/index.ts +172 -0
  27. package/templates/overlays/baas/backend/tsconfig.json +18 -0
  28. package/templates/overlays/baas/package.json +42 -0
  29. package/templates/overlays/baas/pnpm-workspace.yaml +9 -0
  30. package/templates/template/.cursorrules +2 -0
  31. package/templates/template/.dockerignore +1 -1
  32. package/templates/template/.env.example +120 -0
  33. package/templates/template/.github/copilot-instructions.md +2 -0
  34. package/templates/template/.windsurfrules +2 -0
  35. package/templates/template/AGENTS.md +2 -0
  36. package/templates/template/CLAUDE.md +2 -0
  37. package/templates/template/README.md +20 -10
  38. package/templates/template/ai-instructions.md +17 -0
  39. package/templates/template/backend/Dockerfile +5 -4
  40. package/templates/template/backend/functions/hello.ts +36 -32
  41. package/templates/template/backend/package.json +11 -11
  42. package/templates/template/backend/src/env.ts +13 -42
  43. package/templates/template/backend/src/index.ts +54 -28
  44. package/templates/template/config/collections/authors.ts +2 -2
  45. package/templates/template/config/collections/index.ts +25 -4
  46. package/templates/template/config/collections/posts.ts +15 -35
  47. package/templates/template/config/collections/presets/blank/index.ts +3 -0
  48. package/templates/template/config/collections/presets/ecommerce/categories.ts +40 -0
  49. package/templates/template/config/collections/presets/ecommerce/index.ts +6 -0
  50. package/templates/template/config/collections/presets/ecommerce/orders.ts +66 -0
  51. package/templates/template/config/collections/presets/ecommerce/products.ts +64 -0
  52. package/templates/template/config/collections/tags.ts +3 -5
  53. package/templates/template/config/collections/users.ts +142 -0
  54. package/templates/template/config/index.ts +1 -1
  55. package/templates/template/docker-compose.yml +14 -39
  56. package/templates/template/frontend/Dockerfile +5 -3
  57. package/templates/template/frontend/package.json +8 -7
  58. package/templates/template/frontend/src/App.tsx +26 -105
  59. package/templates/template/frontend/src/main.tsx +4 -0
  60. package/templates/template/frontend/src/virtual.d.ts +6 -0
  61. package/templates/template/frontend/tsconfig.json +3 -2
  62. package/templates/template/frontend/vite.config.ts +47 -4
  63. package/templates/template/package.json +22 -8
  64. package/templates/template/pnpm-workspace.yaml +9 -0
  65. package/templates/template/scripts/example.ts +5 -2
  66. package/dist/auth.d.ts +0 -5
  67. package/dist/commands/cli.test.d.ts +0 -1
  68. package/dist/commands/dev.test.d.ts +0 -1
  69. package/dist/commands/init.test.d.ts +0 -1
  70. package/dist/index.cjs +0 -1203
  71. package/dist/index.cjs.map +0 -1
  72. package/dist/utils/project.test.d.ts +0 -1
  73. package/templates/template/.env.template +0 -62
  74. package/templates/template/backend/drizzle.config.ts +0 -22
@@ -7,8 +7,8 @@ A [Rebase](https://rebase.pro) project with a PostgreSQL backend.
7
7
  ### Option 1: Docker (recommended for production)
8
8
 
9
9
  ```bash
10
- cp .env.template .env
11
- # Edit .env — set POSTGRES_PASSWORD and JWT_SECRET (see comments for generators)
10
+ cp .env.example .env
11
+ # Edit .env — set JWT_SECRET and DATABASE_URL (see comments for generators)
12
12
 
13
13
  docker compose up -d
14
14
  ```
@@ -23,35 +23,35 @@ That's it. Your app is running:
23
23
  #### Prerequisites
24
24
 
25
25
  - [Node.js](https://nodejs.org) >= 18
26
- - [pnpm](https://pnpm.io)
27
- - A PostgreSQL database
26
+ - [pnpm](https://pnpm.io) or [npm](https://www.npmjs.com) (v7+)
27
+ - A PostgreSQL database (you can start the included database container via `docker compose up -d db`)
28
28
 
29
29
  #### Setup
30
30
 
31
31
  1. Install dependencies:
32
32
 
33
33
  ```bash
34
- pnpm install
34
+ pnpm install # or: npm install
35
35
  ```
36
36
 
37
37
  2. Configure environment:
38
38
 
39
39
  ```bash
40
- cp .env.template .env
40
+ cp .env.example .env
41
41
  # Edit .env — set DATABASE_URL, JWT_SECRET
42
42
  ```
43
43
 
44
44
  3. Generate schema and push to database:
45
45
 
46
46
  ```bash
47
- rebase schema generate
48
- rebase db push
47
+ pnpm run schema:generate # or: npm run schema:generate
48
+ pnpm run db:push # or: npm run db:push
49
49
  ```
50
50
 
51
51
  4. Start the dev server:
52
52
 
53
53
  ```bash
54
- pnpm dev
54
+ pnpm dev # or: npm run dev
55
55
  ```
56
56
 
57
57
  Backend (Hono + PostgreSQL) on port 3001, frontend (Vite + React) on port 5173.
@@ -69,7 +69,7 @@ Backend (Hono + PostgreSQL) on port 3001, frontend (Vite + React) on port 5173.
69
69
  ├── config/ # Shared collection definitions
70
70
  │ └── collections/ # Schema-as-Code TypeScript files
71
71
  ├── docker-compose.yml # Production stack (Postgres + Backend + Frontend)
72
- ├── .env.template # Environment variable template
72
+ ├── .env.example # Environment variable reference
73
73
  └── package.json # Root workspace config
74
74
  ```
75
75
 
@@ -94,6 +94,16 @@ Call from the client SDK: `client.call("functions/hello", { name: "World" })`
94
94
 
95
95
  Collections are defined once in `config/collections/` and used by both the frontend and backend. This ensures your schema stays in sync across the stack.
96
96
 
97
+ ## Environment Configuration
98
+
99
+ All configuration is managed through a single `.env` file in the project root. Both the backend and frontend read from this file:
100
+
101
+ - **Backend**: loads via `dotenv` from `../../.env` (relative to `backend/src/`)
102
+ - **Frontend**: Vite reads `VITE_*` variables via `envDir` pointing to the project root
103
+ - **Scripts**: load via `dotenv` from the project root
104
+
105
+ Copy `.env.example` to `.env` to get started. See the comments in `.env.example` for details on each variable.
106
+
97
107
  ## Production Deployment
98
108
 
99
109
  ```bash
@@ -0,0 +1,17 @@
1
+ # Rebase AI Coding Rules
2
+
3
+ Rebase provides agent skills for AI coding assistants (Cursor, Claude Code, Windsurf, Gemini CLI, Antigravity, and more) with detailed guidelines, architecture, SDK APIs, and troubleshooting guides.
4
+
5
+ To install skills for your environment, run:
6
+
7
+ ```
8
+ rebase skills install
9
+ ```
10
+
11
+ ## Core Tenets (Quick Reference)
12
+
13
+ 1. **Schema-as-Code**: Always define or edit collections in `config/collections/` (e.g., `config/collections/posts.ts`). Never modify generated Drizzle schemas or PostgreSQL tables manually.
14
+ 2. **Two-Step Migrations**:
15
+ - Step 1: Run `rebase schema generate` to compile collections to the Drizzle schema.
16
+ - Step 2: Run `rebase db push` (development) or `rebase db generate && rebase db migrate` (production) to apply schema changes to the database.
17
+ 3. **Use the SDK**: Always use the Rebase SDK (`rebase.data.<slug>`) to fetch or modify data. Bypassing it with raw SQL or direct Drizzle/PG queries circumvents model validations, lifecycle hooks, and Row-Level Security (RLS).
@@ -14,12 +14,12 @@ ENV PATH="$PNPM_HOME:$PATH"
14
14
  RUN corepack enable
15
15
 
16
16
  # Native dependencies for bcrypt, pg, etc.
17
- RUN apk add --no-cache python3 make g++
17
+ RUN apk add --no-cache python3 make g++ curl ca-certificates
18
18
 
19
19
  WORKDIR /app
20
20
 
21
21
  # Copy workspace root files first (cache-friendly layer)
22
- COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
22
+ COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
23
23
 
24
24
  # Copy workspace packages
25
25
  COPY backend ./backend
@@ -33,7 +33,7 @@ RUN pnpm --filter "*-config" run build
33
33
  RUN pnpm --filter "*-backend" run build
34
34
 
35
35
  # Prune dev dependencies for a smaller runtime
36
- RUN pnpm install --frozen-lockfile --prod
36
+ RUN CI=true pnpm install --frozen-lockfile --prod
37
37
 
38
38
  # ── Stage 2: Production Runtime ──────────────────────────────────────
39
39
  FROM node:22-alpine AS runtime
@@ -41,6 +41,7 @@ FROM node:22-alpine AS runtime
41
41
  ENV PNPM_HOME="/pnpm"
42
42
  ENV PATH="$PNPM_HOME:$PATH"
43
43
  ENV NODE_ENV=production
44
+ ENV CI=true
44
45
 
45
46
  RUN corepack enable
46
47
 
@@ -50,7 +51,7 @@ RUN addgroup -g 1001 rebase && adduser -u 1001 -G rebase -s /bin/sh -D rebase
50
51
  WORKDIR /app
51
52
 
52
53
  # Copy only production artifacts
53
- COPY --from=builder /app/package.json /app/pnpm-lock.yaml /app/pnpm-workspace.yaml ./
54
+ COPY --from=builder /app/package.json /app/pnpm-lock.yaml /app/pnpm-workspace.yaml /app/.npmrc ./
54
55
  COPY --from=builder /app/node_modules ./node_modules
55
56
  COPY --from=builder /app/backend ./backend
56
57
  COPY --from=builder /app/config ./config
@@ -1,6 +1,4 @@
1
- import { Hono } from "hono";
2
- import type { HonoEnv } from "@rebasepro/server-core";
3
- import { rebase } from "@rebasepro/server-core";
1
+ import { defineFunction } from "@rebasepro/server";
4
2
 
5
3
  /**
6
4
  * Example custom function route.
@@ -12,41 +10,47 @@ import { rebase } from "@rebasepro/server-core";
12
10
  * Call from the client SDK:
13
11
  * const result = await client.call("functions/hello", { name: "World" });
14
12
  *
15
- * This is a standard Hono app use any Hono middleware,
16
- * define any HTTP methods, access the request/response directly.
13
+ * Authored with `defineFunction`, which hands you a pre-typed Hono app
14
+ * (so `c.get("user")` / `c.get("driver")` are typed) and the `rebase`
15
+ * singleton via the injected context — use any Hono middleware, define any
16
+ * HTTP methods, access the request/response directly.
17
17
  *
18
- * The `rebase` singleton gives you admin-level access to all
19
- * app-scoped services (data, auth, storage, email) from anywhere.
20
- * For request-scoped / RLS-scoped access, use c.get("user") and c.get("driver").
18
+ * `rebase.dataAsAdmin` gives you admin-level access to your data and
19
+ * **bypasses RLS** use it only for trusted admin work. For request-scoped /
20
+ * RLS-scoped data access, use c.get("user") and c.get("driver"), which carry
21
+ * the caller's identity. (`rebase` also exposes auth, storage, email.)
21
22
  */
22
- const app = new Hono<HonoEnv>();
23
+ export default defineFunction((app, { rebase }) => {
24
+ void rebase; // available for dataAsAdmin/auth/storage/email — see commented usage below
23
25
 
24
- app.post("/", async (c) => {
25
- const body = await c.req.json().catch(() => ({}));
26
- const user = c.get("user");
26
+ app.post("/", async (c) => {
27
+ const body = await c.req.json().catch(() => ({}));
28
+ const user = c.get("user");
27
29
 
28
- const userId = (user && typeof user === "object" && "userId" in user)
29
- ? user.userId
30
- : "anonymous";
30
+ const userId = (user && typeof user === "object" && "userId" in user)
31
+ ? user.userId
32
+ : "anonymous";
31
33
 
32
- // Access any Rebase service just import and use:
33
- // await rebase.email?.send({
34
- // to: "admin@example.com",
35
- // subject: "Function called",
36
- // html: `<p>Hello from ${userId}!</p>`,
37
- // });
38
- //
39
- // const authors = await rebase.data.authors.find({ limit: 5 });
34
+ // Access any Rebase service via the injected `rebase`:
35
+ // await rebase.email.send({
36
+ // to: "admin@example.com",
37
+ // subject: "Function called",
38
+ // html: `<p>Hello from ${userId}!</p>`,
39
+ // });
40
+ //
41
+ // Admin-scoped data (bypasses RLS trusted work only):
42
+ // const authors = await rebase.dataAsAdmin.authors.find({ limit: 5 });
43
+ // For user-scoped data (RLS applies), use the request-scoped driver
44
+ // (c.get("driver")), which carries the caller's identity.
40
45
 
41
- return c.json({
42
- message: `Hello, ${body.name || "World"}!`,
43
- user: userId
46
+ return c.json({
47
+ message: `Hello, ${body.name || "World"}!`,
48
+ user: userId
49
+ });
44
50
  });
45
- });
46
51
 
47
- app.get("/", (c) => {
48
- return c.json({ status: "ok",
49
- endpoint: "hello" });
52
+ app.get("/", (c) => {
53
+ return c.json({ status: "ok",
54
+ endpoint: "hello" });
55
+ });
50
56
  });
51
-
52
- export default app;
@@ -6,26 +6,26 @@
6
6
  "type": "module",
7
7
  "scripts": {
8
8
  "dev": "tsx watch --watch=\"../config/**/*\" src/index.ts",
9
- "build": "tsc --noEmit",
10
- "start": "tsx src/index.ts"
9
+ "build": "rebase schema generate --collections ../config/collections && tsc",
10
+ "start": "node dist/backend/src/index.js"
11
11
  },
12
12
  "dependencies": {
13
- "{{PROJECT_NAME}}-config": "0.0.1",
14
- "@rebasepro/server-core": "workspace:*",
15
- "@rebasepro/server-postgresql": "workspace:*",
16
- "drizzle-orm": "^0.44.4",
17
- "hono": "^4.2.0",
18
- "@hono/node-server": "^1.11.0",
13
+ "{{PROJECT_NAME}}-config": "*",
14
+ "@rebasepro/server": "workspace:*",
15
+ "@rebasepro/server-postgres": "workspace:*",
16
+ "@rebasepro/types": "workspace:*",
17
+ "drizzle-orm": "^0.45.2",
18
+ "hono": "^4.12.10",
19
+ "@hono/node-server": "^1.19.12",
19
20
  "pg": "^8.11.3",
20
21
  "ws": "^8.16.0",
21
- "zod": "^3.22.4",
22
- "dotenv": "^16.0.0"
22
+ "dotenv": "^16.0.0",
23
+ "zod": "^4.4.3"
23
24
  },
24
25
  "devDependencies": {
25
26
  "@types/pg": "^8.6.5",
26
27
  "@types/node": "^20.10.5",
27
28
  "@types/ws": "^8.5.10",
28
- "drizzle-kit": "^0.31.4",
29
29
  "tsx": "^4.20.6",
30
30
  "typescript": "^5.9.2"
31
31
  }
@@ -1,52 +1,23 @@
1
- import { z } from "zod";
2
1
  import * as dotenv from "dotenv";
3
2
  import path from "path";
4
3
  import { fileURLToPath } from "url";
4
+ import { loadEnv } from "@rebasepro/server";
5
+ import { z } from "zod";
5
6
 
6
7
  const __filename = fileURLToPath(import.meta.url);
7
8
  const __dirname = path.dirname(__filename);
8
9
 
9
10
  dotenv.config({ path: path.resolve(__dirname, "../../.env") });
10
11
 
11
- const envSchema = z.object({
12
- NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
13
- PORT: z.string().default("3001").transform(Number),
14
- DATABASE_URL: z.string().url("DATABASE_URL must be a valid URL"),
15
- ADMIN_CONNECTION_STRING: z.string().url().optional(),
16
- JWT_SECRET: z.string().min(32, "JWT_SECRET must be at least 32 characters long"),
17
- JWT_ACCESS_EXPIRES_IN: z.string().default("1h"),
18
- JWT_REFRESH_EXPIRES_IN: z.string().default("30d"),
19
- GOOGLE_CLIENT_ID: z.string().optional(),
20
- ALLOW_REGISTRATION: z.enum(["true", "false", ""]).default("false").transform(v => v === "true"),
21
- CORS_ORIGINS: z.string().optional(),
22
- FRONTEND_URL: z.string().optional(),
23
- DB_POOL_MAX: z.string().default("20").transform(Number),
24
- DB_POOL_IDLE_TIMEOUT: z.string().default("30000").transform(Number),
25
- DB_POOL_CONNECT_TIMEOUT: z.string().default("10000").transform(Number),
26
- FORCE_LOCAL_STORAGE: z.enum(["true", "false", ""]).optional().transform(v => v === "true"),
27
- STORAGE_TYPE: z.enum(["local", "s3"]).default("local"),
28
- STORAGE_PATH: z.string().optional(),
29
- S3_BUCKET: z.string().optional(),
30
- S3_REGION: z.string().optional(),
31
- S3_ACCESS_KEY_ID: z.string().optional(),
32
- S3_SECRET_ACCESS_KEY: z.string().optional(),
33
- S3_ENDPOINT: z.string().url().optional(),
34
- S3_FORCE_PATH_STYLE: z.enum(["true", "false", ""]).optional().transform(v => v === "true"),
35
- /**
36
- * Service key for admin-level script / server-to-server authentication.
37
- * When set, scripts can send `Authorization: Bearer <key>` to get admin access.
38
- * Generate with: node -e "console.log(require('crypto').randomBytes(48).toString('base64'))"
39
- */
40
- REBASE_SERVICE_KEY: z.string().min(32, "REBASE_SERVICE_KEY must be at least 32 characters").optional()
41
- }).superRefine((data, ctx) => {
42
- if (data.NODE_ENV === "production" && !data.CORS_ORIGINS && !data.FRONTEND_URL) {
43
- ctx.addIssue({
44
- code: z.ZodIssueCode.custom,
45
- message: "CORS_ORIGINS or FRONTEND_URL must be set in production to secure the API.",
46
- path: ["CORS_ORIGINS"]
47
- });
48
- }
12
+ export const env = loadEnv({
13
+ extend: z.object({
14
+ SMTP_HOST: z.string().optional(),
15
+ SMTP_PORT: z.string().default("587").transform(Number),
16
+ SMTP_SECURE: z.enum(["true", "false", ""]).default("false").transform(v => v === "true"),
17
+ SMTP_USER: z.string().optional(),
18
+ SMTP_PASS: z.string().optional(),
19
+ SMTP_FROM: z.string().optional(),
20
+ SMTP_NAME: z.string().optional(),
21
+ APP_NAME: z.string().default("Rebase")
22
+ })
49
23
  });
50
-
51
- // Parse and export
52
- export const env = envSchema.parse(process.env);
@@ -7,25 +7,36 @@ import path from "path";
7
7
  import { fileURLToPath } from "url";
8
8
  import {
9
9
  initializeRebaseBackend,
10
+ installShutdownHandlers,
10
11
  serveSPA,
11
12
  HonoEnv,
12
13
  listenWithPortRetry,
13
14
  cleanupDevPortFile,
14
15
  logger
15
- } from "@rebasepro/server-core";
16
- import { createPostgresDatabaseConnection, createPostgresBootstrapper } from "@rebasepro/server-postgresql";
17
- import { enums, relations, tables } from "./schema.generated";
18
- import { env } from "./env";
16
+ } from "@rebasepro/server";
17
+ import { createPostgresDatabaseConnection, createPostgresAdapter } from "@rebasepro/server-postgres";
18
+ import { enums, relations, tables } from "./schema.generated.js";
19
+ import { env } from "./env.js";
20
+ import usersCollection from "../../config/collections/users.js";
19
21
 
20
22
  const __filename = fileURLToPath(import.meta.url);
21
23
  const __dirname = path.dirname(__filename);
22
24
 
23
25
  // ─── App ─────────────────────────────────────────────────────────────
24
- const app = new Hono<HonoEnv>();
26
+ const app: Hono<HonoEnv> = new Hono<HonoEnv>();
25
27
 
26
28
  const isProduction = env.NODE_ENV === "production";
27
29
  const allowedOrigins = isProduction
28
- ? (env.CORS_ORIGINS || env.FRONTEND_URL || "https://yourdomain.com").split(",").map(s => s.trim())
30
+ ? (() => {
31
+ const origins = env.CORS_ORIGINS || env.FRONTEND_URL;
32
+ if (!origins) {
33
+ throw new Error(
34
+ "CORS_ORIGINS or FRONTEND_URL must be set in production. " +
35
+ "Example: CORS_ORIGINS=https://yourdomain.com"
36
+ );
37
+ }
38
+ return origins.split(",").map(s => s.trim());
39
+ })()
29
40
  : [];
30
41
 
31
42
  app.use("/*", cors({
@@ -54,26 +65,47 @@ async function startServer() {
54
65
  functionsDir: path.resolve(__dirname, "../functions"),
55
66
  server,
56
67
  app,
57
- bootstrappers: [
58
- createPostgresBootstrapper({
59
- connection: db,
60
- schema: { tables,
68
+ database: createPostgresAdapter({
69
+ connection: db,
70
+ schema: { tables,
61
71
  enums,
62
72
  relations },
63
- adminConnectionString: env.ADMIN_CONNECTION_STRING || databaseUrl,
64
- connectionString
65
- })
66
- ],
73
+ adminConnectionString: env.ADMIN_CONNECTION_STRING || databaseUrl,
74
+ connectionString
75
+ }),
67
76
  auth: {
77
+ collection: usersCollection,
68
78
  jwtSecret,
69
79
  accessExpiresIn: env.JWT_ACCESS_EXPIRES_IN,
70
80
  refreshExpiresIn: env.JWT_REFRESH_EXPIRES_IN,
71
81
  serviceKey: env.REBASE_SERVICE_KEY,
82
+ // Cookie-based auth: the refresh token is stored in an httpOnly
83
+ // cookie (not readable by JS) instead of localStorage, so it is
84
+ // not exposed to XSS. The frontend opts in via
85
+ // `authFlowMode: "cookie"` on createRebaseClient. Requires CORS
86
+ // `credentials: true` (set above).
87
+ cookieAuth: { sameSite: "Lax" },
72
88
  google: env.GOOGLE_CLIENT_ID
73
89
  ? { clientId: env.GOOGLE_CLIENT_ID }
74
90
  : undefined,
75
- seedDefaultRoles: true,
76
- allowRegistration: env.ALLOW_REGISTRATION
91
+ allowRegistration: env.ALLOW_REGISTRATION,
92
+ email: env.SMTP_HOST
93
+ ? {
94
+ from: env.SMTP_FROM || `${env.APP_NAME} <noreply@rebase.pro>`,
95
+ smtp: {
96
+ host: env.SMTP_HOST,
97
+ port: env.SMTP_PORT,
98
+ secure: env.SMTP_SECURE,
99
+ auth: env.SMTP_USER
100
+ ? { user: env.SMTP_USER,
101
+ pass: env.SMTP_PASS! }
102
+ : undefined,
103
+ name: env.SMTP_NAME
104
+ },
105
+ appName: env.APP_NAME,
106
+ resetPasswordUrl: env.FRONTEND_URL
107
+ }
108
+ : undefined
77
109
  },
78
110
  storage: env.STORAGE_TYPE === "s3"
79
111
  ? {
@@ -111,7 +143,7 @@ relations },
111
143
  if (!isProduction) {
112
144
  // Dev mode: retry the next port if the current one is in use
113
145
  const projectRoot = path.resolve(__dirname, "../..");
114
- const actualPort = await listenWithPortRetry(server, PORT, { portFileDir: projectRoot });
146
+ const actualPort = await listenWithPortRetry(server, PORT, { portFileDir: projectRoot, serviceKey: env.REBASE_SERVICE_KEY });
115
147
 
116
148
  // Clean up port file on exit
117
149
  const cleanup = () => cleanupDevPortFile(projectRoot);
@@ -127,17 +159,11 @@ relations },
127
159
  }
128
160
 
129
161
  // ─── Graceful Shutdown ───────────────────────────────────────────────
130
- // Uses the framework's built-in shutdown() which drains connections,
131
- // stops the cron scheduler, and force-exits after 15s timeout.
132
- const gracefulShutdown = async (signal: string) => {
133
- logger.info(`Received ${signal}, shutting down...`);
134
- await backend.shutdown();
135
- await pool.end();
136
- logger.info("Database pool closed");
137
- process.exit(0);
138
- };
139
- process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
140
- process.on("SIGINT", () => gracefulShutdown("SIGINT"));
162
+ // Drains HTTP, stops crons, tears down realtime, then closes the pool.
163
+ // Guards against double signals and force-exits if shutdown hangs.
164
+ installShutdownHandlers(backend, {
165
+ onCleanup: () => pool.end()
166
+ });
141
167
  }
142
168
 
143
169
  startServer().catch(err => {
@@ -1,6 +1,6 @@
1
- import { EntityCollection } from "@rebasepro/types";
1
+ import { CollectionConfig } from "@rebasepro/types";
2
2
 
3
- const authorsCollection: EntityCollection = {
3
+ const authorsCollection: CollectionConfig = {
4
4
  name: "Authors",
5
5
  singularName: "Author",
6
6
  slug: "authors",
@@ -1,5 +1,26 @@
1
- import postsCollection from "./posts";
2
- import authorsCollection from "./authors";
3
- import tagsCollection from "./tags";
1
+ import postsCollection from "./posts.js";
2
+ import authorsCollection from "./authors.js";
3
+ import tagsCollection from "./tags.js";
4
+ import usersCollection from "./users.js";
5
+ import type { SecurityRule } from "@rebasepro/types";
4
6
 
5
- export const collections = [postsCollection, authorsCollection, tagsCollection];
7
+ export const collections = [postsCollection, authorsCollection, tagsCollection, usersCollection];
8
+
9
+ /**
10
+ * Applied to any collection in this directory that declares no
11
+ * `securityRules` of its own: anyone can read, only admins can write.
12
+ *
13
+ * These live here, next to the collections, because `rebase db push`
14
+ * generates the Postgres policies from these files — that is what actually
15
+ * enforces access. A default declared on the server could never reach the
16
+ * database.
17
+ *
18
+ * A collection that declares neither its own rules nor inherits these is
19
+ * locked by default: admin-only.
20
+ */
21
+ export const defaultSecurityRules: SecurityRule[] = [
22
+ { operation: "select",
23
+ access: "public" },
24
+ { operations: ["insert", "update", "delete"],
25
+ roles: ["admin"] }
26
+ ];
@@ -1,8 +1,8 @@
1
- import { EntityCollection } from "@rebasepro/types";
2
- import authorsCollection from "./authors";
3
- import tagsCollection from "./tags";
1
+ import { CollectionConfig } from "@rebasepro/types";
2
+ import authorsCollection from "./authors.js";
3
+ import tagsCollection from "./tags.js";
4
4
 
5
- const postsCollection: EntityCollection = {
5
+ const postsCollection: CollectionConfig = {
6
6
  name: "Posts",
7
7
  singularName: "Post",
8
8
  slug: "posts",
@@ -12,9 +12,7 @@ const postsCollection: EntityCollection = {
12
12
  id: {
13
13
  name: "ID",
14
14
  type: "number",
15
- validation: {
16
- required: true
17
- }
15
+ isId: "increment"
18
16
  },
19
17
  title: {
20
18
  name: "Title",
@@ -26,7 +24,9 @@ const postsCollection: EntityCollection = {
26
24
  content: {
27
25
  name: "Content",
28
26
  type: "string",
29
- multiline: true
27
+ ui: {
28
+ markdown: true
29
+ }
30
30
  },
31
31
  status: {
32
32
  name: "Status",
@@ -35,57 +35,37 @@ const postsCollection: EntityCollection = {
35
35
  {
36
36
  id: "draft",
37
37
  label: "Draft",
38
- color: "grayLight"
38
+ color: "gray"
39
39
  },
40
40
  {
41
41
  id: "review",
42
42
  label: "In Review",
43
- color: "orangeLight"
43
+ color: "orange"
44
44
  },
45
45
  {
46
46
  id: "published",
47
47
  label: "Published",
48
- color: "greenLight"
48
+ color: "green"
49
49
  }
50
50
  ]
51
51
  },
52
52
  author: {
53
53
  name: "Author",
54
54
  type: "relation",
55
- relationName: "author",
56
- relation: {
57
- relationName: "author",
58
- cardinality: "one",
59
- direction: "owning",
60
- target: () => authorsCollection
61
- }
62
- },
63
- tags: {
64
- name: "Tags",
65
- type: "relation",
66
- relationName: "tags",
67
- relation: {
68
- relationName: "tags",
69
- cardinality: "many",
70
- direction: "owning",
71
- target: () => tagsCollection
72
- }
73
- }
74
- },
75
- relations: [
76
- {
77
55
  relationName: "author",
78
56
  target: () => authorsCollection,
79
57
  cardinality: "one",
80
58
  direction: "owning"
81
59
  },
82
- {
60
+ tags: {
61
+ name: "Tags",
62
+ type: "relation",
83
63
  relationName: "tags",
84
64
  target: () => tagsCollection,
85
65
  cardinality: "many",
86
66
  direction: "owning"
87
67
  }
88
- ]
68
+ }
89
69
  };
90
70
 
91
71
  export default postsCollection;
@@ -0,0 +1,3 @@
1
+ import usersCollection from "../../users.js";
2
+
3
+ export const collections = [usersCollection];
@@ -0,0 +1,40 @@
1
+ import { CollectionConfig } from "@rebasepro/types";
2
+
3
+ const categoriesCollection: CollectionConfig = {
4
+ name: "Categories",
5
+ singularName: "Category",
6
+ slug: "categories",
7
+ table: "categories",
8
+ icon: "Category",
9
+ properties: {
10
+ id: {
11
+ name: "ID",
12
+ type: "number",
13
+ isId: "increment"
14
+ },
15
+ name: {
16
+ name: "Name",
17
+ type: "string",
18
+ validation: { required: true }
19
+ },
20
+ slug: {
21
+ name: "Slug",
22
+ type: "string",
23
+ validation: { required: true }
24
+ },
25
+ description: {
26
+ name: "Description",
27
+ type: "string",
28
+ ui: {
29
+ multiline: true
30
+ }
31
+ },
32
+ icon: {
33
+ name: "Icon",
34
+ type: "string"
35
+ }
36
+ },
37
+ propertiesOrder: ["id", "name", "slug", "description", "icon"]
38
+ };
39
+
40
+ export default categoriesCollection;
@@ -0,0 +1,6 @@
1
+ import productsCollection from "./products.js";
2
+ import categoriesCollection from "./categories.js";
3
+ import ordersCollection from "./orders.js";
4
+ import usersCollection from "../../users.js";
5
+
6
+ export const collections = [productsCollection, categoriesCollection, ordersCollection, usersCollection];