@rebasepro/cli 0.16.0 → 0.16.1-canary.g0d7af95

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 (54) hide show
  1. package/dist/bundle.d.ts +28 -2
  2. package/dist/commands/build.d.ts +10 -0
  3. package/dist/commands/cloud/context.d.ts +17 -1
  4. package/dist/commands/cloud/databases.d.ts +1 -0
  5. package/dist/commands/cloud/deploy.d.ts +58 -0
  6. package/dist/commands/cloud/deployments.d.ts +42 -0
  7. package/dist/commands/cloud/env.d.ts +1 -0
  8. package/dist/commands/cloud/extensions.d.ts +1 -0
  9. package/dist/commands/cloud/projects.d.ts +14 -4
  10. package/dist/commands/cloud/resources.d.ts +10 -1
  11. package/dist/commands/db.d.ts +16 -0
  12. package/dist/commands/dev.d.ts +11 -0
  13. package/dist/commands/doctor.d.ts +1 -1
  14. package/dist/commands/init.d.ts +1 -1
  15. package/dist/commands/resources.d.ts +1 -0
  16. package/dist/constraints-BK1_4vci.js +80 -0
  17. package/dist/constraints-BK1_4vci.js.map +1 -0
  18. package/dist/daemon-Bdl4lrdt.js +252 -0
  19. package/dist/daemon-Bdl4lrdt.js.map +1 -0
  20. package/dist/daemon-entry-Brq-S8XX.js +378 -0
  21. package/dist/daemon-entry-Brq-S8XX.js.map +1 -0
  22. package/dist/dev-db/__fixtures__/cli-entry.d.ts +1 -0
  23. package/dist/dev-db/constraints.d.ts +98 -0
  24. package/dist/dev-db/daemon-entry.d.ts +35 -0
  25. package/dist/dev-db/daemon.d.ts +92 -0
  26. package/dist/dev-db/notification-proxy.d.ts +102 -0
  27. package/dist/dev-db/prepare.d.ts +63 -0
  28. package/dist/dev-db/pull.d.ts +92 -0
  29. package/dist/dev-db/resolve.d.ts +66 -0
  30. package/dist/dev-db/state.d.ts +93 -0
  31. package/dist/function-portability.d.ts +45 -0
  32. package/dist/index.d.ts +17 -17
  33. package/dist/index.es.js +5638 -4098
  34. package/dist/index.es.js.map +1 -1
  35. package/dist/manifest.d.ts +24 -1
  36. package/dist/pull-DqPRu1te.js +167 -0
  37. package/dist/pull-DqPRu1te.js.map +1 -0
  38. package/dist/resources/derive.d.ts +47 -0
  39. package/dist/state-c0CJ6Kwb.js +190 -0
  40. package/dist/state-c0CJ6Kwb.js.map +1 -0
  41. package/dist/telemetry/consent.d.ts +1 -1
  42. package/dist/telemetry/index.d.ts +7 -7
  43. package/dist/utils/dev-preflight.d.ts +73 -0
  44. package/package.json +13 -8
  45. package/templates/eject/backend/src/index.ts +15 -8
  46. package/templates/eject/config/resources.ts +24 -0
  47. package/templates/template/AGENTS.md +1 -1
  48. package/templates/template/CLAUDE.md +1 -1
  49. package/templates/template/README.md +1 -1
  50. package/templates/template/ai-instructions.md +5 -2
  51. package/templates/template/backend/functions/hello.ts +43 -22
  52. package/templates/template/config/resources.ts +57 -0
  53. package/templates/template/docker-compose.yml +10 -1
  54. package/templates/template/gitignore +1 -0
@@ -15,11 +15,14 @@ import {
15
15
  HonoEnv,
16
16
  listenWithPortRetry,
17
17
  cleanupDevPortFile,
18
- loadDeclaredStorageSources,
19
18
  resolveStorageSources,
20
19
  logger
21
20
  } from "@rebasepro/server";
22
21
  import { createPostgresDatabaseConnection, createPostgresAdapter } from "@rebasepro/server-postgres";
22
+ import { declaredStorageSources } from "@rebasepro/types";
23
+ // Side-effect import: declaring is what registers, so anything that dropped
24
+ // this as "unused" would leave the backend with no buckets — silently.
25
+ import "../../config/resources.js";
23
26
  // {{#collections}}
24
27
  import { enums, relations, tables } from "./schema.generated.js";
25
28
  // {{/collections}}
@@ -32,12 +35,11 @@ import usersCollection from "../../config/collections/users.js";
32
35
  const __filename = fileURLToPath(import.meta.url);
33
36
  const __dirname = path.dirname(__filename);
34
37
 
35
- // Which buckets this project has, read from the `storage` block of its own
36
- // `rebase.json`. Declared there rather than here so the platform, the console
37
- // and this process all read one list a custom image ships the repository, so
38
- // the file it already contains is the natural place for it. Absent means one
39
- // default source, configured from the plain S3_*/GCS_* variables.
40
- const storageSources = loadDeclaredStorageSources(__dirname);
38
+ // Which buckets this project has, read from the declarations in
39
+ // `config/resources.ts`. One declaration site, so this process, the platform
40
+ // and the frontend all read the same list. A project that declares none gets
41
+ // one default source from the plain, unsuffixed S3_*/GCS_* variables.
42
+ const storageSources = declaredStorageSources();
41
43
 
42
44
  // ─── App ─────────────────────────────────────────────────────────────
43
45
  const app: Hono<HonoEnv> = new Hono<HonoEnv>();
@@ -181,7 +183,6 @@ pass: env.SMTP_PASS! }
181
183
  storageSources,
182
184
  path.resolve(__dirname, "../../uploads")
183
185
  ),
184
- storageSources,
185
186
  // Storage is not under row-level security, so this hook IS its access
186
187
  // model — the server refuses to boot in production without one, because
187
188
  // "signed in" would otherwise be the only thing between a visitor and
@@ -201,6 +202,12 @@ pass: env.SMTP_PASS! }
201
202
  // import { requireAuth, requireAdmin } from "@rebasepro/server";
202
203
  // app.get("/admin/report", requireAuth, requireAdmin, handler);
203
204
  //
205
+ // From the package root here, deliberately — **not** from
206
+ // `@rebasepro/server/functions`. The guards on that subpath read an
207
+ // identity that a Rebase router has already resolved, which is the right
208
+ // thing inside `backend/functions/` and useless out here, where no such
209
+ // middleware ran. These verify the token themselves.
210
+ //
204
211
  // `requireAuth` answers 401 without a valid token; `requireAdmin` answers
205
212
  // 403 without the `admin` role and must follow `requireAuth`. Note that
206
213
  // `c.get("driver")` — the driver carrying the caller's identity — is only
@@ -0,0 +1,24 @@
1
+ /**
2
+ * What this project needs from wherever it runs.
3
+ *
4
+ * One declaration per resource, and this is the only place they are declared.
5
+ * The backend reads them, `rebase resources --write` records them in
6
+ * `rebase.resources.json` for a host to read before it runs anything, and the
7
+ * frontend can be handed the same list with `declaredStorageSources()`.
8
+ *
9
+ * A database is implicit: a backend has one whether or not this file says so,
10
+ * bound from DATABASE_URL. Name a second one to get a second.
11
+ */
12
+ import { bucket, database } from "@rebasepro/types";
13
+
14
+ /** The project's database. Bound from DATABASE_URL. */
15
+ export const main = database();
16
+
17
+ /**
18
+ * The default bucket, bound from the plain unsuffixed storage variables
19
+ * (`S3_BUCKET`, `GCS_BUCKET`, `STORAGE_BUCKET`).
20
+ *
21
+ * Name more of them to get more — `bucket("media")` reads `S3_BUCKET__MEDIA`,
22
+ * and so on for every variable that kind uses.
23
+ */
24
+ export const files = bucket();
@@ -1,2 +1,2 @@
1
1
  # Rebase AI Rules
2
- Please refer to and follow the instructions defined in [ai-instructions.md](./ai-instructions.md).
2
+ Please refer to and follow the instructions defined in [ai-instructions.md](ai-instructions.md).
@@ -1,2 +1,2 @@
1
1
  # Rebase AI Rules
2
- Please refer to and follow the instructions defined in [ai-instructions.md](./ai-instructions.md).
2
+ Please refer to and follow the instructions defined in [ai-instructions.md](ai-instructions.md).
@@ -87,7 +87,7 @@ Drop a Hono app in `backend/functions/` and it's auto-mounted at `/api/functions
87
87
 
88
88
  ```typescript
89
89
  // backend/functions/hello.ts
90
- import { defineFunction, requireAuth, requireAdmin } from "@rebasepro/server";
90
+ import { defineFunction, requireAuth, requireAdmin } from "@rebasepro/server/functions";
91
91
 
92
92
  export default defineFunction((app) => {
93
93
  // Deliberately public — anyone can call this.
@@ -15,5 +15,8 @@ rebase skills install
15
15
  - Step 1: Run `rebase schema generate` to compile collections to the Drizzle schema.
16
16
  - Step 2: Run `rebase db push` (development) or `rebase db generate && rebase db migrate` (production) to apply schema changes to the database.
17
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).
18
- 4. **Guard every custom route**: routes in `backend/functions/` are mounted **without** an auth requirement — webhook receivers need that — so each one is public until you guard it. Import `requireAuth` / `requireAdmin` from `@rebasepro/server` and pass them in the route's own middleware slot (`app.post("/", requireAuth, handler)`), not via `app.use()`, which only covers routes declared below it. Reading `c.get("user")` is not a guard: an anonymous caller gets `undefined` and the handler still runs. See `backend/functions/hello.ts` for all three tiers.
19
- 5. **Build UI from the kit, never from scratch**: any custom view, home page, dashboard or entity tab must be composed from `@rebasepro/ui` components (`Card`, `Typography`, `Button`, `Chip`, `Alert`, …) and the theme's colour tokens (`text-surface-*`, `bg-surface-accent-*`, `text-primary`), with a `dark:` value beside every light one. Do **not** invent a palette, a type scale, or hand-written CSS: a hardcoded colour like `#111` is invisible in one of the two themes and nothing will catch it. The live reference ships in your `node_modules` — read `@rebasepro/app/src/components/Debug/UIReferenceView.tsx` before building a view, and see [Styling Custom UI](https://rebase.pro/docs/frontend/styling). The `rebase-design-language` and `rebase-ui-components` skills cover this in full; install them with `rebase skills install`.
18
+ 4. **Guard every custom route**: routes in `backend/functions/` are mounted **without** an auth requirement — webhook receivers need that — so each one is public until you guard it. Import `requireAuth` / `requireAdmin` from `@rebasepro/server/functions` and pass them in the route's own middleware slot (`app.post("/", requireAuth, handler)`), not via `app.use()`, which only covers routes declared below it. Reading `getUser(c)` is not a guard: an anonymous caller gets `undefined` and the handler still runs. See `backend/functions/hello.ts` for all three tiers.
19
+ 5. **In `backend/functions/`, always import from `@rebasepro/server/functions`** never from `@rebasepro/server`. Both work today; the subpath is the portable one, and it also gives you the typed context accessors (`getUser`, `getDriver`, `requireDriver`) instead of casting `c.get("user")`. The package root is for a server entrypoint, not for route handlers.
20
+ 6. **Never read `process.env` at the top of a function file.** A module-scope read that comes back undefined throws at import time, and the loader reports that as a *skipped function* — the route simply 404s with no error attached to it. Read configuration inside the handler with `requireEnv(c, "NAME")`, or build a client once with `lazyResource(env => new Client(env.KEY))`.
21
+ 7. **Work that outlives the response goes in `waitUntil(c, promise)`**, not a floating promise. A floating promise is dropped when the process shuts down mid-deploy; `waitUntil` is what a graceful shutdown waits for.
22
+ 8. **Build UI from the kit, never from scratch**: any custom view, home page, dashboard or entity tab must be composed from `@rebasepro/ui` components (`Card`, `Typography`, `Button`, `Chip`, `Alert`, …) and the theme's colour tokens (`text-surface-*`, `bg-surface-accent-*`, `text-primary`), with a `dark:` value beside every light one. Do **not** invent a palette, a type scale, or hand-written CSS: a hardcoded colour like `#111` is invisible in one of the two themes and nothing will catch it. The live reference ships in your `node_modules` — read `@rebasepro/app/src/components/Debug/UIReferenceView.tsx` before building a view, and see [Styling Custom UI](https://rebase.pro/docs/frontend/styling). The `rebase-design-language` and `rebase-ui-components` skills cover this in full; install them with `rebase skills install`.
@@ -1,4 +1,10 @@
1
- import { defineFunction, requireAuth, requireAdmin } from "@rebasepro/server";
1
+ import {
2
+ defineFunction,
3
+ requireAuth,
4
+ requireAdmin,
5
+ getUser,
6
+ getUserId
7
+ } from "@rebasepro/server/functions";
2
8
 
3
9
  /**
4
10
  * Example custom function route.
@@ -11,17 +17,24 @@ import { defineFunction, requireAuth, requireAdmin } from "@rebasepro/server";
11
17
  * Call from the client SDK:
12
18
  * const result = await client.call("functions/hello", { name: "World" });
13
19
  *
14
- * Authored with `defineFunction`, which hands you a pre-typed Hono app
15
- * (so `c.get("user")` / `c.get("driver")` are typed) and the `rebase`
16
- * singleton via the injected context — use any Hono middleware, define any
17
- * HTTP methods, access the request/response directly.
20
+ * Authored with `defineFunction`, which hands you a pre-typed Hono app (so
21
+ * `c.get("user")` / `c.get("driver")` are typed) and the `rebase` singleton via
22
+ * the injected context — use any Hono middleware, define any HTTP methods,
23
+ * access the request/response directly.
24
+ *
25
+ * **Import from `@rebasepro/server/functions`, not `@rebasepro/server`.** Same
26
+ * code, but that entry point is the portable one: it pulls in nothing that
27
+ * needs Node, so a function written against it can run on any JavaScript
28
+ * runtime. The package root reaches the whole framework — the boot sequence,
29
+ * the file loaders, the WebSocket layer — which is correct for a server
30
+ * entrypoint and is not what a route handler needs.
18
31
  *
19
32
  * **Custom functions are not authenticated for you.** The functions router
20
33
  * parses the caller's token and puts the result in the context, but it does
21
34
  * not reject anonymous requests — a webhook receiver (Stripe, GitHub) has no
22
35
  * token to send, and that has to keep working. So every route in this folder
23
- * is public until you say otherwise, and reading `c.get("user")` is not a
24
- * check: an anonymous caller just gets `undefined` and the handler runs anyway.
36
+ * is public until you say otherwise, and reading `getUser(c)` is not a check:
37
+ * an anonymous caller just gets `undefined` and the handler runs anyway.
25
38
  *
26
39
  * Say otherwise with `requireAuth` / `requireAdmin`, in the route's own
27
40
  * middleware slot as below. `requireAuth` answers 401 without a valid token.
@@ -35,19 +48,25 @@ import { defineFunction, requireAuth, requireAdmin } from "@rebasepro/server";
35
48
  * bypass** — your policies are still evaluated, just against that identity. So
36
49
  * `policy.serverContext()` is false for it (that arm means "no uid at all"),
37
50
  * and anything an `admin` user can reach, it can reach too. For request-scoped
38
- * data access use c.get("user") and c.get("driver"), which carry the caller's
39
- * identity. `rebase.sql()` is the real bypass — owner connection, no policies.
40
- * (`rebase` also exposes auth, storage, email.)
51
+ * data access use `getUser(c)` and `getDriver(c)`, which carry the caller's
52
+ * identity. `rebase.sql()` is the real bypass — owner connection, no policies,
53
+ * and the one accessor that is Node-only. (`rebase` also exposes auth, storage,
54
+ * email.)
55
+ *
56
+ * Two habits worth keeping from the start, because both are free here and
57
+ * expensive to retrofit later:
58
+ *
59
+ * - Read configuration **inside** a handler — `requireEnv(c, "STRIPE_KEY")`
60
+ * or `lazyResource(...)` — never `process.env.X` at the top of the file. A
61
+ * module-scope read that comes back undefined takes the whole file down at
62
+ * import time, and the loader reports that only as "skipped".
63
+ * - Wrap work that outlives the response in `waitUntil(c, promise)` rather
64
+ * than leaving a floating promise. It is what lets a graceful shutdown wait
65
+ * for your webhook instead of dropping it.
41
66
  */
42
67
  export default defineFunction((app, { rebase }) => {
43
68
  void rebase; // available for dataAsAdmin/auth/storage/email — see commented usage below
44
69
 
45
- /** The caller's id, or undefined when nobody is signed in. */
46
- const uidOf = (user: unknown): string | undefined =>
47
- (typeof user === "object" && user !== null && "uid" in user)
48
- ? (user as { uid?: string }).uid
49
- : undefined;
50
-
51
70
  // ── Public ────────────────────────────────────────────────────────────
52
71
  // Deliberately public: no guard, so anyone can call it. That is a fine
53
72
  // choice for health probes, webhook receivers and public content — the
@@ -68,17 +87,17 @@ export default defineFunction((app, { rebase }) => {
68
87
  // await rebase.email.send({
69
88
  // to: "admin@example.com",
70
89
  // subject: "Function called",
71
- // html: `<p>Hello from ${uidOf(c.get("user"))}!</p>`,
90
+ // html: `<p>Hello from ${getUserId(c)}!</p>`,
72
91
  // });
73
92
  //
74
- // Admin-scoped data (bypasses RLS — trusted work only):
93
+ // Admin-scoped data (RLS evaluated as the `admin` role — trusted work
94
+ // only):
75
95
  // const authors = await rebase.dataAsAdmin.authors.find({ limit: 5 });
76
- // For user-scoped data (RLS applies), use the request-scoped driver
77
- // (c.get("driver")), which carries the caller's identity.
96
+ // For user-scoped data (RLS applies as the caller), use getDriver(c).
78
97
 
79
98
  return c.json({
80
99
  message: `Hello, ${body.name || "World"}!`,
81
- user: uidOf(c.get("user"))
100
+ user: getUserId(c)
82
101
  });
83
102
  });
84
103
 
@@ -86,6 +105,8 @@ export default defineFunction((app, { rebase }) => {
86
105
  // Order matters: `requireAuth` first (401 for anonymous), then
87
106
  // `requireAdmin` (403 for a signed-in non-admin).
88
107
  app.get("/stats", requireAuth, requireAdmin, (c) => {
89
- return c.json({ admin: uidOf(c.get("user")) });
108
+ // `getUser` returns a narrowed `{ uid, roles, ...claims }` — no cast,
109
+ // and `roles` is always an array.
110
+ return c.json({ admin: getUser(c)?.uid });
90
111
  });
91
112
  });
@@ -0,0 +1,57 @@
1
+ /**
2
+ * What this project needs from wherever it runs.
3
+ *
4
+ * A resource is declared here once — that a database exists, that a bucket
5
+ * exists, what engine backs it — and bound somewhere else: to a real connection
6
+ * string and a real bucket name, per environment, from the environment. The
7
+ * split is the point. Your staging and your production run the same commit and
8
+ * must not share a bucket, so *which* bucket can never live in a committed file;
9
+ * *that* there is one is a property of your code and belongs beside it.
10
+ *
11
+ * The runtime reads this file at boot and reports, for every declaration,
12
+ * whether it is bound. Nothing here is silent.
13
+ */
14
+ import { bucket, database } from "@rebasepro/types";
15
+
16
+ /**
17
+ * The project's database, bound from `DATABASE_URL`.
18
+ *
19
+ * Implicit — a backend has one whether or not this line is here. It is written
20
+ * out so that the second one has somewhere obvious to go.
21
+ */
22
+ export const main = database();
23
+
24
+ /**
25
+ * A second database, if you ever want one.
26
+ *
27
+ * Bound from `DATABASE_URL__ANALYTICS`: the key, uppercased, after a double
28
+ * underscore. Every resource follows that rule, and the default-keyed one reads
29
+ * the plain unsuffixed variable.
30
+ *
31
+ * export const analytics = database("analytics");
32
+ */
33
+
34
+ /**
35
+ * Object storage.
36
+ *
37
+ * Uncomment to give this project a bucket. The default-keyed one binds from the
38
+ * plain `S3_BUCKET` / `GCS_BUCKET` / `STORAGE_BUCKET`; a named one appends its
39
+ * key, so `bucket("media")` reads `S3_BUCKET__MEDIA`.
40
+ *
41
+ * export const files = bucket({ engine: "s3" });
42
+ * export const media = bucket("media", { engine: "s3" });
43
+ *
44
+ * ## When you have several on one provider
45
+ *
46
+ * Name an `account` and they share one credential set instead of repeating it:
47
+ *
48
+ * export const media = bucket("media", { engine: "s3", account: "minio" });
49
+ * export const avatars = bucket("avatars", { engine: "s3", account: "minio" });
50
+ *
51
+ * That reads `S3_BUCKET__MEDIA` and `S3_BUCKET__AVATARS` — each bucket keeps its
52
+ * own name — while `S3_ACCESS_KEY_ID__MINIO`, `S3_SECRET_ACCESS_KEY__MINIO` and
53
+ * `S3_ENDPOINT__MINIO` are read once for both. Rotating that key is then one
54
+ * edit rather than one per bucket. A per-bucket value still wins where you set
55
+ * one, so a single source can be moved to another provider without breaking the
56
+ * rest off their shared account.
57
+ */
@@ -30,7 +30,16 @@ name: {{PROJECT_NAME}}
30
30
  services:
31
31
  # ── PostgreSQL ───────────────────────────────────────────────────────
32
32
  db:
33
- image: postgres:18-alpine
33
+ # pgvector's image rather than the plain `postgres:18-alpine`: it is the
34
+ # official Postgres image with the `vector` extension already built in, on
35
+ # the same major. A `{ type: "vector" }` property compiles to `VECTOR(n)`,
36
+ # which a stock Postgres answers with `type "vector" does not exist` — a
37
+ # boot failure whose cause is an image, not a config. Nothing here installs
38
+ # extensions on your behalf, so the image is where that decision lives.
39
+ #
40
+ # Swapping this for `postgres:18-alpine` is safe on the same major if you
41
+ # never declare a vector property.
42
+ image: pgvector/pgvector:pg18
34
43
  restart: unless-stopped
35
44
  environment:
36
45
  POSTGRES_USER: rebase_app
@@ -29,6 +29,7 @@ uploads/
29
29
  # Rebase dev
30
30
  .rebase-dev-url
31
31
  .rebase-dev-port
32
+ .rebase-dev-secrets.json
32
33
 
33
34
  # Built project bundle (`rebase build`)
34
35
  dist-bundle/