@rebasepro/cli 0.23.0 → 0.24.1-canary.g56af4a4

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 (158) hide show
  1. package/README.md +19 -3
  2. package/bin/rebase.js +4 -1
  3. package/dist/agent-setup-B31F00DC.js +324 -0
  4. package/dist/agent-setup-B31F00DC.js.map +1 -0
  5. package/dist/api-keys-BPBi5nOF.js +524 -0
  6. package/dist/api-keys-BPBi5nOF.js.map +1 -0
  7. package/dist/apps-CPXNElX4.js +256 -0
  8. package/dist/apps-CPXNElX4.js.map +1 -0
  9. package/dist/args-BnmsEXvu.js +109 -0
  10. package/dist/args-BnmsEXvu.js.map +1 -0
  11. package/dist/atlas-dev-database-DTMuEScS.js +62 -0
  12. package/dist/atlas-dev-database-DTMuEScS.js.map +1 -0
  13. package/dist/auth-DvY94ZuL.js +395 -0
  14. package/dist/auth-DvY94ZuL.js.map +1 -0
  15. package/dist/branch-pointer-DIhKbQLp.js +2 -0
  16. package/dist/build-CFwUx-Ff.js +336 -0
  17. package/dist/build-CFwUx-Ff.js.map +1 -0
  18. package/dist/bundle-BQhP_xiA.js +1617 -0
  19. package/dist/bundle-BQhP_xiA.js.map +1 -0
  20. package/dist/bundle.d.ts +6 -2
  21. package/dist/cli.d.ts +23 -2
  22. package/dist/cloud-CkTr-yt0.js +9640 -0
  23. package/dist/cloud-CkTr-yt0.js.map +1 -0
  24. package/dist/collection-drift-BKj_BoMq.js +106 -0
  25. package/dist/collection-drift-BKj_BoMq.js.map +1 -0
  26. package/dist/commands/__test_support__/parser-specs.d.ts +13 -0
  27. package/dist/commands/agent-setup.d.ts +116 -0
  28. package/dist/commands/api-keys.d.ts +86 -23
  29. package/dist/commands/apps.d.ts +17 -0
  30. package/dist/commands/auth.d.ts +7 -0
  31. package/dist/commands/build.d.ts +5 -1
  32. package/dist/commands/cloud/auth.d.ts +4 -3
  33. package/dist/commands/cloud/bundle-deploy.d.ts +45 -0
  34. package/dist/commands/cloud/context.d.ts +44 -5
  35. package/dist/commands/cloud/cron.d.ts +53 -0
  36. package/dist/commands/cloud/debug.d.ts +134 -0
  37. package/dist/commands/cloud/index.d.ts +89 -12
  38. package/dist/commands/cloud/token-capabilities.d.ts +76 -0
  39. package/dist/commands/cloud/tokens.d.ts +23 -0
  40. package/dist/commands/db.d.ts +52 -25
  41. package/dist/commands/dev.d.ts +181 -7
  42. package/dist/commands/doctor.d.ts +5 -0
  43. package/dist/commands/generate_sdk.d.ts +29 -2
  44. package/dist/commands/init.d.ts +57 -14
  45. package/dist/commands/schema.d.ts +6 -0
  46. package/dist/commands/skills.d.ts +126 -7
  47. package/dist/commands/start.d.ts +29 -0
  48. package/dist/commands/storage.d.ts +46 -0
  49. package/dist/consent-BWHaYx5p.js +76 -0
  50. package/dist/consent-BWHaYx5p.js.map +1 -0
  51. package/dist/context-BqhRydBg.js +1234 -0
  52. package/dist/context-BqhRydBg.js.map +1 -0
  53. package/dist/daemon-DBn-shxX.js +2 -0
  54. package/dist/{daemon-entry-LTFKtpHy.js → daemon-entry-Da-D3rmH.js} +3 -4
  55. package/dist/daemon-entry-Da-D3rmH.js.map +1 -0
  56. package/dist/db-CMpBK3sv.js +1252 -0
  57. package/dist/db-CMpBK3sv.js.map +1 -0
  58. package/dist/derive-RPIsAVO4.js +474 -0
  59. package/dist/derive-RPIsAVO4.js.map +1 -0
  60. package/dist/dev-C9P5Yp9k.js +1593 -0
  61. package/dist/dev-C9P5Yp9k.js.map +1 -0
  62. package/dist/dev-db/atlas-dev-database.d.ts +32 -0
  63. package/dist/dev-db/daemon-entry.d.ts +13 -0
  64. package/dist/dev-db/daemon.d.ts +10 -0
  65. package/dist/dev-db/prepare.d.ts +10 -0
  66. package/dist/dev-preflight-N1evJL3S.js +2 -0
  67. package/dist/doctor-DYs-NqFI.js +771 -0
  68. package/dist/doctor-DYs-NqFI.js.map +1 -0
  69. package/dist/eject-BT6t37D0.js +335 -0
  70. package/dist/eject-BT6t37D0.js.map +1 -0
  71. package/dist/fold-static-C1EtHlfV.js +201 -0
  72. package/dist/fold-static-C1EtHlfV.js.map +1 -0
  73. package/dist/fold-static.d.ts +26 -3
  74. package/dist/function-portability-BIvlvR9q.js +265 -0
  75. package/dist/function-portability-BIvlvR9q.js.map +1 -0
  76. package/dist/generate_sdk-UV635zSe.js +477 -0
  77. package/dist/generate_sdk-UV635zSe.js.map +1 -0
  78. package/dist/index.es.js +189 -22095
  79. package/dist/index.es.js.map +1 -1
  80. package/dist/init-fXeBcxuN.js +1042 -0
  81. package/dist/init-fXeBcxuN.js.map +1 -0
  82. package/dist/manifest-DzlC60WX.js +871 -0
  83. package/dist/manifest-DzlC60WX.js.map +1 -0
  84. package/dist/manifest.d.ts +41 -1
  85. package/dist/normalize-imports-CmVdE9iZ.js +100 -0
  86. package/dist/normalize-imports-CmVdE9iZ.js.map +1 -0
  87. package/dist/package-manager-DSEOwTqM.js +193 -0
  88. package/dist/package-manager-DSEOwTqM.js.map +1 -0
  89. package/dist/path-flags-DUBuI2i1.js +77 -0
  90. package/dist/path-flags-DUBuI2i1.js.map +1 -0
  91. package/dist/precompress.d.ts +17 -0
  92. package/dist/prepare-Btn9Ewbn.js +1052 -0
  93. package/dist/prepare-Btn9Ewbn.js.map +1 -0
  94. package/dist/{project-3WCbrrfW.js → project-Wp2zoyeN.js} +34 -184
  95. package/dist/project-Wp2zoyeN.js.map +1 -0
  96. package/dist/resolve-CJLkfFV0.js +2 -0
  97. package/dist/resources-DT1COn_6.js +161 -0
  98. package/dist/resources-DT1COn_6.js.map +1 -0
  99. package/dist/schema-BDBWvCHu.js +125 -0
  100. package/dist/schema-BDBWvCHu.js.map +1 -0
  101. package/dist/skills-DTFibT1m.js +642 -0
  102. package/dist/skills-DTFibT1m.js.map +1 -0
  103. package/dist/start-rU2e3Kp5.js +228 -0
  104. package/dist/start-rU2e3Kp5.js.map +1 -0
  105. package/dist/{state-C59Elrnt.js → state-Do7ZJYkF.js} +49 -4
  106. package/dist/state-Do7ZJYkF.js.map +1 -0
  107. package/dist/status-BR9oyi-y.js +351 -0
  108. package/dist/status-BR9oyi-y.js.map +1 -0
  109. package/dist/storage-DyvrSTH3.js +303 -0
  110. package/dist/storage-DyvrSTH3.js.map +1 -0
  111. package/dist/telemetry-CAWNCugs.js +338 -0
  112. package/dist/telemetry-CAWNCugs.js.map +1 -0
  113. package/dist/telemetry-DbLqGIBW.js +129 -0
  114. package/dist/telemetry-DbLqGIBW.js.map +1 -0
  115. package/dist/unknown-command-DNwDcX3w.js +98 -0
  116. package/dist/unknown-command-DNwDcX3w.js.map +1 -0
  117. package/dist/upgrade-D-5-Od96.js +1265 -0
  118. package/dist/upgrade-D-5-Od96.js.map +1 -0
  119. package/dist/upgrade.d.ts +14 -2
  120. package/dist/utils/path-flags.d.ts +18 -0
  121. package/dist/utils/project.d.ts +16 -0
  122. package/dist/version-Buyyo1ut.js +58 -0
  123. package/dist/version-Buyyo1ut.js.map +1 -0
  124. package/package.json +7 -7
  125. package/templates/eject/backend/src/env.ts +1 -1
  126. package/templates/eject/backend/src/index.ts +23 -2
  127. package/templates/overlays/baas/ai-instructions.md +1 -1
  128. package/templates/overlays/baas/config/storage.ts +5 -3
  129. package/templates/template/.cursorrules +1 -1
  130. package/templates/template/.env.example +10 -6
  131. package/templates/template/.github/copilot-instructions.md +1 -1
  132. package/templates/template/.mcp.json +2 -2
  133. package/templates/template/.windsurfrules +1 -1
  134. package/templates/template/AGENTS.md +1 -1
  135. package/templates/template/CLAUDE.md +1 -1
  136. package/templates/template/README.md +11 -12
  137. package/templates/template/ai-instructions.md +5 -4
  138. package/templates/template/backend/functions/hello.ts +8 -0
  139. package/templates/template/backend/package.json +1 -1
  140. package/templates/template/config/storage.ts +5 -3
  141. package/templates/template/docker-compose.yml +6 -6
  142. package/templates/template/frontend/vite.config.ts +11 -3
  143. package/templates/template/gitignore +6 -1
  144. package/templates/template/package.json +4 -3
  145. package/templates/template/scripts/example.ts +6 -4
  146. package/dist/branch-pointer-dBiClJ0p.js +0 -111
  147. package/dist/branch-pointer-dBiClJ0p.js.map +0 -1
  148. package/dist/constraints-DKGbNfUW.js +0 -48
  149. package/dist/constraints-DKGbNfUW.js.map +0 -1
  150. package/dist/daemon-DR9Tk0La.js +0 -355
  151. package/dist/daemon-DR9Tk0La.js.map +0 -1
  152. package/dist/daemon-entry-LTFKtpHy.js.map +0 -1
  153. package/dist/dev-preflight-CNLl4rdo.js +0 -367
  154. package/dist/dev-preflight-CNLl4rdo.js.map +0 -1
  155. package/dist/project-3WCbrrfW.js.map +0 -1
  156. package/dist/resolve-BAeRUBhg.js +0 -74
  157. package/dist/resolve-BAeRUBhg.js.map +0 -1
  158. package/dist/state-C59Elrnt.js.map +0 -1
@@ -7,10 +7,11 @@
7
7
  | Command | What it does |
8
8
  |---|---|
9
9
  | `pnpm dev` | Backend, frontend and the managed development database, together |
10
- | `pnpm schema:generate` then `pnpm db:push` | The same loop with `pnpm dev` not running, plus the changes boot leaves alone: collections → Drizzle schema → development database |
10
+ | `pnpm schema:generate` then `pnpm generate:sdk` | The same loop with `pnpm dev` not running: collections → Drizzle schema and SDK types. The next boot creates what is missing |
11
+ | `pnpm db:push` | The changes boot leaves alone (a rename, a narrowed type, a removed field). Needs your own PostgreSQL in `DATABASE_URL`; it refuses the managed development database |
11
12
  | `pnpm db:generate` then `pnpm db:migrate` | The production path — write the migration file, then apply it |
12
13
  | `pnpm schema:introspect` | The other direction: existing tables → collection definitions |
13
- | `pnpm generate:sdk` | Regenerate the typed client from the collections |
14
+ | `pnpm generate:sdk` | Regenerate the typed client in `generated/sdk/` from the collections. `pnpm dev` already does this on start and on every save; the script is for CI and for when `pnpm dev` is not running |
14
15
  | `pnpm build` then `pnpm start` | Build the deployable bundle, then run it |
15
16
  | `pnpm skills:install` | Install the Rebase skills for your assistant |
16
17
  | `pnpm example` | Run `scripts/example.ts` against the running backend — the SDK, end to end |
@@ -29,8 +30,8 @@
29
30
 
30
31
  ## Core rules
31
32
 
32
- 1. **Schema-as-code**: define and edit collections in `config/collections/` with `defineCollection` from `@rebasepro/cms-types`, and register each one in that directory's `index.ts` — a collection missing from the barrel does not exist. Never hand-edit generated schemas or Postgres tables.
33
- 2. **Applying a collection change**: while `pnpm dev` is running, saving a file in `config/collections/` is the whole step — it regenerates `backend/src/schema.generated.ts`, restarts the backend, and boot creates the tables and columns that are missing. With `pnpm dev` stopped, that is `pnpm schema:generate`. `pnpm db:push` is only for what boot deliberately leaves alone — a renamed column, a narrowed type, a removed field, junction-table RLS — and it needs your own PostgreSQL, not the managed development database. In production the pair is `pnpm db:generate` then `pnpm db:migrate`.
33
+ 1. **Schema-as-code**: define and edit collections in `config/collections/` with `defineCollection` from `@rebasepro/cms-types`, one collection per file, default-exported. Every file in that directory is loaded — by the backend, the admin panel and the SDK generator — whether or not `index.ts` lists it: the barrel is a sort order for the admin navigation (and holds `defaultSecurityRules`), not a registry. To remove a collection, delete its file. Never hand-edit generated schemas or Postgres tables.
34
+ 2. **Applying a collection change**: while `pnpm dev` is running, saving a file in `config/collections/` is the whole step — it regenerates `backend/src/schema.generated.ts` and the SDK types in `generated/sdk/`, restarts the backend, and boot creates the tables and columns that are missing. With `pnpm dev` stopped, that is `pnpm schema:generate` and `pnpm generate:sdk`. `pnpm db:push` is only for what boot deliberately leaves alone — a renamed column, a narrowed type, a removed field — and it needs your own PostgreSQL, not the managed development database. In production the pair is `pnpm db:generate` then `pnpm db:migrate`.
34
35
  3. **Use the SDK**: server-side that is `rebase.dataAsAdmin.<slug>` for work done as the service identity, or `getDriver(c)` inside a function when the read should run as the caller. The server client has no plain `data` accessor — it is omitted precisely so that the choice of identity is written down.
35
36
  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.
36
37
  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.
@@ -47,6 +47,14 @@ import {
47
47
  * slot over `app.use("/*", requireAuth)`: `use()` only covers routes declared
48
48
  * *below* it, so a route appended later above it is silently unprotected.
49
49
  *
50
+ * An API key is a caller too. It reaches this file only if it holds the
51
+ * `functions:invoke` scope (or `functions:invoke:hello`), it passes
52
+ * `requireAuth`, and a service key created with `--roles admin` passes
53
+ * `requireAdmin`. To let a key do one thing here and not another, declare a
54
+ * scope under `auth.scopes` on the users collection and add
55
+ * `requireScope("reports:read")` to that route — every signed-in person holds
56
+ * every app scope, so it narrows keys, not people.
57
+ *
50
58
  * `rebase.dataAsAdmin` gives you admin-level access to your data: it runs as
51
59
  * `{ uid: "service", roles: ["admin"] }`, which is **admin-scoped, not an RLS
52
60
  * bypass** — your policies are still evaluated, just against that identity. So
@@ -4,7 +4,7 @@
4
4
  "description": "Rebase backend with PostgreSQL",
5
5
  "type": "module",
6
6
  "scripts": {
7
- "build": "rebase schema generate --collections ../config/collections && tsc && rebase normalize-imports dist"
7
+ "build": "rebase schema generate && tsc && rebase normalize-imports dist"
8
8
  },
9
9
  "dependencies": {
10
10
  "@ariga/atlas": "^1.2.3",
@@ -1,4 +1,4 @@
1
- import { isPublicStoragePath, type StorageAuthorize } from "@rebasepro/types";
1
+ import type { StorageAuthorize } from "@rebasepro/types";
2
2
 
3
3
  /**
4
4
  * Who may do what to files in storage.
@@ -37,7 +37,7 @@ import { isPublicStoragePath, type StorageAuthorize } from "@rebasepro/types";
37
37
  *
38
38
  * ```ts
39
39
  * export const storageAuthorize: StorageAuthorize = async ({ key, user, operation, data }) => {
40
- * if (operation === "read" && isPublicStoragePath(key)) return true;
40
+ * if (operation === "read" && key.startsWith("public/")) return true;
41
41
  * if (!user) return false;
42
42
  *
43
43
  * // Writes land under the caller's own prefix.
@@ -66,7 +66,9 @@ import { isPublicStoragePath, type StorageAuthorize } from "@rebasepro/types";
66
66
  export const storageAuthorize: StorageAuthorize = ({ key, user, operation }) => {
67
67
  // The `public/` prefix is the framework's convention for world-readable
68
68
  // objects, and it is what signed download URLs for public assets rely on.
69
- if (operation === "read" && isPublicStoragePath(key)) {
69
+ // `key` is the canonical key the server serves, so a prefix test is exact:
70
+ // `default/public/x` is a private object in a folder named `default`.
71
+ if (operation === "read" && key.startsWith("public/")) {
70
72
  return true;
71
73
  }
72
74
 
@@ -15,7 +15,7 @@
15
15
  # docker compose up # boot creates the tables it is missing
16
16
  #
17
17
  # `rebase db push` is the separate step for what boot deliberately leaves
18
- # alone: junction-table RLS, and any change that is not purely additive.
18
+ # alone: any change that is not purely additive.
19
19
  #
20
20
  # One container then serves the API at /api and the admin at / — same origin, so
21
21
  # there is no CORS to configure between them and no second web server.
@@ -132,12 +132,12 @@ services:
132
132
  DISABLE_SELF_REGISTRATION: ${DISABLE_SELF_REGISTRATION:-true}
133
133
 
134
134
  # `ensure` (the default) creates missing tables and columns at boot —
135
- # your collections' as well as auth's — and is additive only: nothing is
136
- # dropped, narrowed or rewritten, because that must never happen as a
137
- # side effect of a container restart.
135
+ # your collections' as well as auth's — with their row-level security,
136
+ # and is additive only: nothing is dropped, narrowed or rewritten,
137
+ # because that must never happen as a side effect of a container restart.
138
138
  #
139
- # `rebase db push` is still what applies the rest: junction-table RLS on
140
- # many-to-many relations, and any change that is not purely additive.
139
+ # `rebase db push` is still what applies the rest: any change that is not
140
+ # purely additive.
141
141
  # Set this to `none` to take a deployment out of provisioning entirely.
142
142
  REBASE_MIGRATE_ON_BOOT: ${REBASE_MIGRATE_ON_BOOT:-ensure}
143
143
 
@@ -30,9 +30,9 @@ export default defineConfig({
30
30
  "@remix-run/router"
31
31
  ]
32
32
  },
33
- esbuild: {
34
- logOverride: { "this-is-undefined-in-esm": "silent" }
35
- },
33
+ // No `esbuild` block: Vite 8 transforms with oxc and ignores it, and
34
+ // having one made every `rebase dev` and build print "Both esbuild and oxc
35
+ // options were set".
36
36
  build: {
37
37
  minify: true,
38
38
  outDir: "./dist",
@@ -75,6 +75,14 @@ export default defineConfig({
75
75
  // anchor a heavy one to the critical path.
76
76
  if (id.includes("commonjsHelpers") || id.includes("commonjs-dynamic-modules")) return "vendor-commonjs-helpers";
77
77
 
78
+ // Small libraries the entry needs, named before the heavy groups. A name
79
+ // takes the unassigned dependencies of its modules with it, so
80
+ // `prism-react-renderer` took `clsx` — which the kit imports statically — and
81
+ // the entry imported 85 kB of syntax highlighter for a 633-byte helper;
82
+ // React core landed in `vendor-dnd` the same way.
83
+ if (/[\\/]node_modules[\\/](react|scheduler|use-sync-external-store)[\\/]/.test(id)) return "vendor-react";
84
+ if (/[\\/]node_modules[\\/]clsx[\\/]/.test(id)) return "vendor-clsx";
85
+
78
86
  if (id.includes("exceljs")) return "vendor-exceljs";
79
87
  if (id.includes("prosemirror")) return "vendor-prosemirror";
80
88
  if (id.includes("monaco-editor") || id.includes("@monaco-editor")) return "vendor-monaco";
@@ -11,8 +11,13 @@ build/
11
11
  !.env.example
12
12
 
13
13
  # IDE
14
+ #
15
+ # `.vscode/*` rather than `.vscode/`: git cannot re-include a file inside an
16
+ # ignored directory, and `.vscode/mcp.json` is the Rebase MCP server
17
+ # `rebase init --agent copilot` registers for the whole team.
14
18
  .idea/
15
- .vscode/
19
+ .vscode/*
20
+ !.vscode/mcp.json
16
21
  *.swp
17
22
  *.swo
18
23
 
@@ -13,11 +13,11 @@
13
13
  "dev": "rebase dev",
14
14
  "build": "rebase build",
15
15
  "start": "rebase start",
16
- "db:generate": "rebase db generate --collections ../config/collections",
16
+ "db:generate": "rebase db generate",
17
17
  "db:migrate": "rebase db migrate",
18
18
  "schema:introspect": "rebase schema introspect",
19
- "db:push": "rebase db push --collections ../config/collections",
20
- "schema:generate": "rebase schema generate --collections ../config/collections",
19
+ "db:push": "rebase db push",
20
+ "schema:generate": "rebase schema generate",
21
21
  "generate:sdk": "rebase generate-sdk",
22
22
  "skills:install": "rebase skills install",
23
23
  "example": "tsx scripts/example.ts",
@@ -25,6 +25,7 @@
25
25
  },
26
26
  "devDependencies": {
27
27
  "@rebasepro/cli": "workspace:*",
28
+ "@rebasepro/mcp": "workspace:*",
28
29
  "@rebasepro/types": "workspace:*",
29
30
  "tsx": "^4.20.6",
30
31
  "typescript": "^6.0.3"
@@ -23,13 +23,15 @@ import fs from "node:fs";
23
23
  import path from "node:path";
24
24
  import * as dotenv from "dotenv";
25
25
  import { createRebaseClient } from "@rebasepro/client";
26
- // Optional: fully typed collections. `rebase generate-sdk` writes these under
27
- // ./generated/sdk — the dictionary is what maps a hyphenated slug to the
28
- // property name, so a client built without it 404s at runtime.
26
+ // Optional: fully typed collections. `rebase dev` writes these under
27
+ // ./generated/sdk (`rebase generate-sdk` outside it) — the dictionary is what
28
+ // maps a hyphenated slug to the property name, so a client built without it
29
+ // 404s at runtime.
29
30
  // import { collectionsDictionary, type Database } from "../generated/sdk/database.types";
30
31
 
31
32
  // Load .env from project root (same file the backend uses)
32
- dotenv.config({ path: path.resolve(process.cwd(), ".env") });
33
+ // `quiet`: dotenv 17 otherwise prints its own advertisement banner.
34
+ dotenv.config({ path: path.resolve(process.cwd(), ".env"), quiet: true });
33
35
 
34
36
  // ─── Resolve Backend URL ─────────────────────────────────────────────
35
37
  let baseUrl = process.env.REBASE_URL;
@@ -1,111 +0,0 @@
1
- import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import path from "path";
3
- import fs from "fs";
4
- //#region src/dev-db/branch-pointer.ts
5
- /**
6
- * Which database branch this checkout is working on.
7
- *
8
- * `rebase db branch create` made a full copy of the database in about a second
9
- * and then stopped one step short of being a feature: there was no `switch`, no
10
- * `--branch` on `rebase dev`, and nothing wrote the branch anywhere a command
11
- * would read it. The only way to *use* a branch was to hand-edit
12
- * `DATABASE_URL`, and the documentation claimed otherwise — "the CLI updates
13
- * your local development configuration" — which it did not; the `.env` was
14
- * byte-identical afterwards.
15
- *
16
- * This is that missing step, and the shape of it is chosen to avoid three
17
- * things:
18
- *
19
- * 1. **It does not touch `.env`.** That file holds secrets, comments and the
20
- * developer's own ordering, and a command that rewrites it in place will
21
- * eventually lose something that was not its business. The branch is
22
- * recorded beside the other per-checkout state in `.rebase/`, which is
23
- * already gitignored — a branch is a fact about one machine, never about
24
- * the project.
25
- *
26
- * 2. **It stores a name, not a connection string.** The credentials stay in
27
- * exactly one place; the URL is derived by swapping the database name on
28
- * the base one at resolution time. A stored URL would be a second copy of
29
- * the password, and would go stale the moment `.env` changed.
30
- *
31
- * 3. **It does not outrank an explicit instruction.** `--database-url` and a
32
- * `DATABASE_URL` in the shell are more immediate and more specific than a
33
- * switch made yesterday, so they still win. The pointer sits directly above
34
- * the project's `.env`: it is a deliberate, persistent choice, and `.env` is
35
- * the project default it is meant to override. Anything less and `switch`
36
- * would silently do nothing on every project that sets `DATABASE_URL` —
37
- * which is every project not using the managed database.
38
- */
39
- var branch_pointer_exports = /* @__PURE__ */ __exportAll({
40
- BRANCH_DB_PREFIX: () => "rb_",
41
- branchDatabaseName: () => branchDatabaseName,
42
- branchPointerPath: () => branchPointerPath,
43
- branchUrl: () => branchUrl,
44
- clearActiveBranch: () => clearActiveBranch,
45
- databaseNameOf: () => databaseNameOf,
46
- readActiveBranch: () => readActiveBranch,
47
- writeActiveBranch: () => writeActiveBranch
48
- });
49
- /** Where the pointer lives, next to the rest of the per-checkout state. */
50
- function branchPointerPath(projectRoot) {
51
- return path.join(projectRoot, ".rebase", "branch.json");
52
- }
53
- function branchDatabaseName(name) {
54
- return `rb_${name}`;
55
- }
56
- /** Read the active branch, or null when the checkout is on the main database. */
57
- function readActiveBranch(projectRoot) {
58
- try {
59
- const raw = fs.readFileSync(branchPointerPath(projectRoot), "utf-8");
60
- const parsed = JSON.parse(raw);
61
- if (typeof parsed.name !== "string" || typeof parsed.database !== "string") return null;
62
- if (!parsed.name || !parsed.database) return null;
63
- return {
64
- name: parsed.name,
65
- database: parsed.database
66
- };
67
- } catch {
68
- return null;
69
- }
70
- }
71
- function writeActiveBranch(projectRoot, branch) {
72
- const file = branchPointerPath(projectRoot);
73
- fs.mkdirSync(path.dirname(file), { recursive: true });
74
- fs.writeFileSync(file, `${JSON.stringify(branch, null, 2)}\n`, "utf-8");
75
- }
76
- /** Return to the main database. Absent is success — this is idempotent. */
77
- function clearActiveBranch(projectRoot) {
78
- fs.rmSync(branchPointerPath(projectRoot), { force: true });
79
- }
80
- /**
81
- * The base connection string with its database swapped for the branch's.
82
- *
83
- * Everything else is carried across untouched — credentials, host, port, and
84
- * the query string, which is load-bearing here: `?sslmode=disable` is what a
85
- * local Docker Postgres needs, and dropping it turns a switch into a TLS error
86
- * that says nothing about branches.
87
- *
88
- * Returns null for a connection string that cannot be parsed, so the caller can
89
- * say so plainly rather than emitting a mangled URL that fails later.
90
- */
91
- function branchUrl(baseUrl, database) {
92
- try {
93
- const url = new URL(baseUrl);
94
- url.pathname = `/${database}`;
95
- return url.toString();
96
- } catch {
97
- return null;
98
- }
99
- }
100
- /** The database name in a connection string, for reporting what you are on. */
101
- function databaseNameOf(connectionString) {
102
- try {
103
- return new URL(connectionString).pathname.replace(/^\//, "") || null;
104
- } catch {
105
- return null;
106
- }
107
- }
108
- //#endregion
109
- export { branch_pointer_exports as n, readActiveBranch as r, branchUrl as t };
110
-
111
- //# sourceMappingURL=branch-pointer-dBiClJ0p.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"branch-pointer-dBiClJ0p.js","names":[],"sources":["../src/dev-db/branch-pointer.ts"],"sourcesContent":["/**\n * Which database branch this checkout is working on.\n *\n * `rebase db branch create` made a full copy of the database in about a second\n * and then stopped one step short of being a feature: there was no `switch`, no\n * `--branch` on `rebase dev`, and nothing wrote the branch anywhere a command\n * would read it. The only way to *use* a branch was to hand-edit\n * `DATABASE_URL`, and the documentation claimed otherwise — \"the CLI updates\n * your local development configuration\" — which it did not; the `.env` was\n * byte-identical afterwards.\n *\n * This is that missing step, and the shape of it is chosen to avoid three\n * things:\n *\n * 1. **It does not touch `.env`.** That file holds secrets, comments and the\n * developer's own ordering, and a command that rewrites it in place will\n * eventually lose something that was not its business. The branch is\n * recorded beside the other per-checkout state in `.rebase/`, which is\n * already gitignored — a branch is a fact about one machine, never about\n * the project.\n *\n * 2. **It stores a name, not a connection string.** The credentials stay in\n * exactly one place; the URL is derived by swapping the database name on\n * the base one at resolution time. A stored URL would be a second copy of\n * the password, and would go stale the moment `.env` changed.\n *\n * 3. **It does not outrank an explicit instruction.** `--database-url` and a\n * `DATABASE_URL` in the shell are more immediate and more specific than a\n * switch made yesterday, so they still win. The pointer sits directly above\n * the project's `.env`: it is a deliberate, persistent choice, and `.env` is\n * the project default it is meant to override. Anything less and `switch`\n * would silently do nothing on every project that sets `DATABASE_URL` —\n * which is every project not using the managed database.\n */\n\nimport fs from \"fs\";\nimport path from \"path\";\n\n/** Where the pointer lives, next to the rest of the per-checkout state. */\nexport function branchPointerPath(projectRoot: string): string {\n return path.join(projectRoot, \".rebase\", \"branch.json\");\n}\n\nexport interface ActiveBranch {\n /** The name the developer typed. */\n name: string;\n /** The PostgreSQL database that name refers to. */\n database: string;\n}\n\n/**\n * The prefix `BranchService` puts on every branch database.\n *\n * Duplicated from the driver rather than imported because the CLI must answer\n * \"which database\" without loading a database driver — `rebase dev` reads this\n * before it knows which plugin the project uses. It is covered by a test that\n * fails if the driver's prefix moves.\n */\nexport const BRANCH_DB_PREFIX = \"rb_\";\n\nexport function branchDatabaseName(name: string): string {\n return `${BRANCH_DB_PREFIX}${name}`;\n}\n\n/** Read the active branch, or null when the checkout is on the main database. */\nexport function readActiveBranch(projectRoot: string): ActiveBranch | null {\n try {\n const raw = fs.readFileSync(branchPointerPath(projectRoot), \"utf-8\");\n const parsed = JSON.parse(raw) as Partial<ActiveBranch>;\n if (typeof parsed.name !== \"string\" || typeof parsed.database !== \"string\") return null;\n if (!parsed.name || !parsed.database) return null;\n\n return { name: parsed.name, database: parsed.database };\n } catch {\n // Absent is the common case and is not an error. A corrupt file is\n // treated the same way: the answer is \"no branch\", which is the state\n // the developer can always get back to with `branch switch --off`.\n return null;\n }\n}\n\nexport function writeActiveBranch(projectRoot: string, branch: ActiveBranch): void {\n const file = branchPointerPath(projectRoot);\n fs.mkdirSync(path.dirname(file), { recursive: true });\n fs.writeFileSync(file, `${JSON.stringify(branch, null, 2)}\\n`, \"utf-8\");\n}\n\n/** Return to the main database. Absent is success — this is idempotent. */\nexport function clearActiveBranch(projectRoot: string): void {\n fs.rmSync(branchPointerPath(projectRoot), { force: true });\n}\n\n/**\n * The base connection string with its database swapped for the branch's.\n *\n * Everything else is carried across untouched — credentials, host, port, and\n * the query string, which is load-bearing here: `?sslmode=disable` is what a\n * local Docker Postgres needs, and dropping it turns a switch into a TLS error\n * that says nothing about branches.\n *\n * Returns null for a connection string that cannot be parsed, so the caller can\n * say so plainly rather than emitting a mangled URL that fails later.\n */\nexport function branchUrl(baseUrl: string, database: string): string | null {\n try {\n const url = new URL(baseUrl);\n // `pathname` keeps its leading slash; a URL with no database at all\n // still gets one, which is what connecting to a named branch means.\n url.pathname = `/${database}`;\n\n return url.toString();\n } catch {\n return null;\n }\n}\n\n/** The database name in a connection string, for reporting what you are on. */\nexport function databaseNameOf(connectionString: string): string | null {\n try {\n const name = new URL(connectionString).pathname.replace(/^\\//, \"\");\n\n return name || null;\n } catch {\n return null;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCA,SAAgB,kBAAkB,aAA6B;CAC3D,OAAO,KAAK,KAAK,aAAa,WAAW,aAAa;AAC1D;AAmBA,SAAgB,mBAAmB,MAAsB;CACrD,OAAO,MAAsB;AACjC;;AAGA,SAAgB,iBAAiB,aAA0C;CACvE,IAAI;EACA,MAAM,MAAM,GAAG,aAAa,kBAAkB,WAAW,GAAG,OAAO;EACnE,MAAM,SAAS,KAAK,MAAM,GAAG;EAC7B,IAAI,OAAO,OAAO,SAAS,YAAY,OAAO,OAAO,aAAa,UAAU,OAAO;EACnF,IAAI,CAAC,OAAO,QAAQ,CAAC,OAAO,UAAU,OAAO;EAE7C,OAAO;GAAE,MAAM,OAAO;GAAM,UAAU,OAAO;EAAS;CAC1D,QAAQ;EAIJ,OAAO;CACX;AACJ;AAEA,SAAgB,kBAAkB,aAAqB,QAA4B;CAC/E,MAAM,OAAO,kBAAkB,WAAW;CAC1C,GAAG,UAAU,KAAK,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;CACpD,GAAG,cAAc,MAAM,GAAG,KAAK,UAAU,QAAQ,MAAM,CAAC,EAAE,KAAK,OAAO;AAC1E;;AAGA,SAAgB,kBAAkB,aAA2B;CACzD,GAAG,OAAO,kBAAkB,WAAW,GAAG,EAAE,OAAO,KAAK,CAAC;AAC7D;;;;;;;;;;;;AAaA,SAAgB,UAAU,SAAiB,UAAiC;CACxE,IAAI;EACA,MAAM,MAAM,IAAI,IAAI,OAAO;EAG3B,IAAI,WAAW,IAAI;EAEnB,OAAO,IAAI,SAAS;CACxB,QAAQ;EACJ,OAAO;CACX;AACJ;;AAGA,SAAgB,eAAe,kBAAyC;CACpE,IAAI;EAGA,OAFa,IAAI,IAAI,gBAAgB,CAAC,CAAC,SAAS,QAAQ,OAAO,EAExD,KAAQ;CACnB,QAAQ;EACJ,OAAO;CACX;AACJ"}
@@ -1,48 +0,0 @@
1
- //#region src/dev-db/constraints.ts
2
- /**
3
- * Extensions to hand PGlite's constructor.
4
- *
5
- * `CREATE EXTENSION` alone cannot install these — PGlite resolves them from
6
- * bundles supplied at construction time, so anything missing here is missing
7
- * from the database no matter what the migration says.
8
- *
9
- * The module is spelled out per extension rather than derived from the name,
10
- * because they do not all live in the same place: the two contrib ones are
11
- * subpaths of PGlite itself, and pgvector is a package of its own. Deriving the
12
- * path is what left `vector` off this list — `@electric-sql/pglite/contrib/vector`
13
- * does not exist, so a project declaring a `{ type: "vector" }` property could
14
- * not use the managed dev database at all.
15
- */
16
- var PGLITE_EXTENSIONS = [
17
- {
18
- name: "pg_trgm",
19
- module: "@electric-sql/pglite/contrib/pg_trgm",
20
- export: "pg_trgm"
21
- },
22
- {
23
- name: "unaccent",
24
- module: "@electric-sql/pglite/contrib/unaccent",
25
- export: "unaccent"
26
- },
27
- {
28
- name: "vector",
29
- module: "@electric-sql/pglite-pgvector",
30
- export: "vector"
31
- }
32
- ];
33
- /**
34
- * Announced at startup, every time, rather than discovered.
35
- *
36
- * A developer who does not know realtime is off will read the silence as a bug
37
- * in their own code, which is a worse outcome than not offering the managed
38
- * database at all.
39
- */
40
- var MANAGED_LIMITATIONS = [{
41
- id: "concurrency",
42
- summary: "Requests are served one at a time. Behaviour is correct but serialized, so lock contention and job-queue concurrency cannot be reproduced here.",
43
- remedy: "Reproduce concurrency against a real Postgres: rebase dev --docker"
44
- }];
45
- //#endregion
46
- export { PGLITE_EXTENSIONS as n, MANAGED_LIMITATIONS as t };
47
-
48
- //# sourceMappingURL=constraints-DKGbNfUW.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"constraints-DKGbNfUW.js","names":[],"sources":["../src/dev-db/constraints.ts"],"sourcesContent":["/**\n * What PGlite can and cannot do as a development database, measured rather\n * than assumed.\n *\n * Everything in this directory is shaped by four facts, each established by\n * running it against `@electric-sql/pglite` 0.5.6 and\n * `@electric-sql/pglite-socket` 0.2.9 rather than by reading their docs. They\n * are recorded here because two of them are silent failures — the kind that\n * make a developer lose an evening to a feature that reports success and does\n * nothing.\n *\n * 1. **It is really PostgreSQL 18.3.** `select version()` over the socket\n * returns `PostgreSQL 18.3 (PGlite 0.5.6) on wasm32`, which is the same\n * major as the `postgres:18-alpine` the eject template ships. So a dev\n * database here and a compose database there are the same Postgres, and\n * schema behaviour does not diverge between them.\n *\n * 2. **`pg_trgm` and `unaccent` are available**, which is what search\n * collections need. They are not installed by a bare `CREATE EXTENSION`,\n * though — PGlite ships them as separate bundles that must be passed to the\n * constructor, and without that `CREATE EXTENSION pg_trgm` fails with\n * `extension \"pg_trgm\" is not available`. {@link PGLITE_EXTENSIONS} is that\n * list, and it has to stay in step with what the schema generator emits.\n *\n * 3. **RLS is enforced exactly as it is on a real server.** With\n * `SET LOCAL ROLE \"rebase_user\"` inside a transaction — which is how\n * `PostgresBackendDriver` isolates every request — `current_user` becomes\n * the restricted role, `session_user` stays the owner, and a policy using\n * `current_setting('app.tenant')` filters rows correctly, including under\n * `FORCE ROW LEVEL SECURITY`. Measured: an owner saw 3 rows and the\n * role-switched transaction saw 2, with the cross-tenant probe returning 0.\n * This is the one that mattered most: a dev database that quietly failed to\n * apply RLS would give false confidence about the product's central claim.\n *\n * 4. **Concurrency is the real limit, and it fails badly.** PGlite is a single\n * session, and `PGLiteSocketServer` multiplexes connections onto it. Two\n * pooled clients that hold *overlapping transactions* deadlock — not error,\n * hang — which is precisely what a request-per-transaction server does under\n * any concurrent load. {@link MANAGED_POOL_MAX} is the answer: one client\n * connection, so requests queue in the pool instead of deadlocking in the\n * multiplexer. Measured: with a pool of 1, four concurrent queries and a\n * role-switched RLS transaction all pass; with a pool of 5 the same script\n * hangs indefinitely.\n *\n * 5. **LISTEN/NOTIFY needed repairing, and now works.** A notification is an\n * asynchronous message with no request to answer, and the multiplexer hands\n * it to whichever socket is reading rather than to the one that issued\n * `LISTEN` — so a dedicated listener connection, which is exactly how the\n * realtime engine works, received nothing while the *writer* received\n * notifications it never asked for. `notification-proxy.ts` corrects that by\n * copying every `NotificationResponse` frame to every client, which for a\n * single-session database is simply the truth. Realtime therefore works\n * against the managed database, with no change to the server: it does\n * ordinary `LISTEN` over ordinary libpq.\n */\n\n/** One extension bundle, and where to import it from. */\nexport interface PgliteExtension {\n /** The name `CREATE EXTENSION` uses, and the key PGlite is handed. */\n name: string;\n /** Module the bundle is imported from. */\n module: string;\n /** The named export carrying the bundle. */\n export: string;\n}\n\n/**\n * Extensions to hand PGlite's constructor.\n *\n * `CREATE EXTENSION` alone cannot install these — PGlite resolves them from\n * bundles supplied at construction time, so anything missing here is missing\n * from the database no matter what the migration says.\n *\n * The module is spelled out per extension rather than derived from the name,\n * because they do not all live in the same place: the two contrib ones are\n * subpaths of PGlite itself, and pgvector is a package of its own. Deriving the\n * path is what left `vector` off this list — `@electric-sql/pglite/contrib/vector`\n * does not exist, so a project declaring a `{ type: \"vector\" }` property could\n * not use the managed dev database at all.\n */\nexport const PGLITE_EXTENSIONS: readonly PgliteExtension[] = [\n { name: \"pg_trgm\", module: \"@electric-sql/pglite/contrib/pg_trgm\", export: \"pg_trgm\" },\n { name: \"unaccent\", module: \"@electric-sql/pglite/contrib/unaccent\", export: \"unaccent\" },\n { name: \"vector\", module: \"@electric-sql/pglite-pgvector\", export: \"vector\" }\n];\n\n/**\n * Client connections the managed database tolerates: exactly one.\n *\n * Not a tuning choice. Two concurrent transactions over the socket\n * multiplexer deadlock, and a request-per-transaction server produces those\n * the moment two requests overlap. One connection converts that deadlock into\n * ordinary queueing, which is slower and correct.\n */\nexport const MANAGED_POOL_MAX = 1;\n\n/**\n * Connections the socket server will accept.\n *\n * Above {@link MANAGED_POOL_MAX} so that a second *non-transactional* client —\n * `rebase db push` in another terminal while `rebase dev` runs — is refused\n * with a connection error rather than corrupting the multiplexer. The pool\n * limit is what prevents overlapping transactions; this only stops a stampede.\n */\nexport const MANAGED_SERVER_MAX_CONNECTIONS = 4;\n\n/** What a managed PGlite database cannot do, in the words the user needs. */\nexport interface ManagedLimitation {\n /** Stable id, so a warning can be suppressed or tested for. */\n id: string;\n /** One line, naming the feature rather than the mechanism. */\n summary: string;\n /** What to do instead. Always a concrete command. */\n remedy: string;\n}\n\n/**\n * Announced at startup, every time, rather than discovered.\n *\n * A developer who does not know realtime is off will read the silence as a bug\n * in their own code, which is a worse outcome than not offering the managed\n * database at all.\n */\nexport const MANAGED_LIMITATIONS: readonly ManagedLimitation[] = [\n {\n id: \"concurrency\",\n summary:\n \"Requests are served one at a time. Behaviour is correct but serialized, so \" +\n \"lock contention and job-queue concurrency cannot be reproduced here.\",\n remedy: \"Reproduce concurrency against a real Postgres: rebase dev --docker\"\n }\n] as const;\n"],"mappings":";;;;;;;;;;;;;;;AAgFA,IAAa,oBAAgD;CACzD;EAAE,MAAM;EAAW,QAAQ;EAAwC,QAAQ;CAAU;CACrF;EAAE,MAAM;EAAY,QAAQ;EAAyC,QAAQ;CAAW;CACxF;EAAE,MAAM;EAAU,QAAQ;EAAiC,QAAQ;CAAS;AAChF;;;;;;;;AAuCA,IAAa,sBAAoD,CAC7D;CACI,IAAI;CACJ,SACI;CAEJ,QAAQ;AACZ,CACJ"}