@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.
- package/README.md +19 -3
- package/bin/rebase.js +4 -1
- package/dist/agent-setup-B31F00DC.js +324 -0
- package/dist/agent-setup-B31F00DC.js.map +1 -0
- package/dist/api-keys-BPBi5nOF.js +524 -0
- package/dist/api-keys-BPBi5nOF.js.map +1 -0
- package/dist/apps-CPXNElX4.js +256 -0
- package/dist/apps-CPXNElX4.js.map +1 -0
- package/dist/args-BnmsEXvu.js +109 -0
- package/dist/args-BnmsEXvu.js.map +1 -0
- package/dist/atlas-dev-database-DTMuEScS.js +62 -0
- package/dist/atlas-dev-database-DTMuEScS.js.map +1 -0
- package/dist/auth-DvY94ZuL.js +395 -0
- package/dist/auth-DvY94ZuL.js.map +1 -0
- package/dist/branch-pointer-DIhKbQLp.js +2 -0
- package/dist/build-CFwUx-Ff.js +336 -0
- package/dist/build-CFwUx-Ff.js.map +1 -0
- package/dist/bundle-BQhP_xiA.js +1617 -0
- package/dist/bundle-BQhP_xiA.js.map +1 -0
- package/dist/bundle.d.ts +6 -2
- package/dist/cli.d.ts +23 -2
- package/dist/cloud-CkTr-yt0.js +9640 -0
- package/dist/cloud-CkTr-yt0.js.map +1 -0
- package/dist/collection-drift-BKj_BoMq.js +106 -0
- package/dist/collection-drift-BKj_BoMq.js.map +1 -0
- package/dist/commands/__test_support__/parser-specs.d.ts +13 -0
- package/dist/commands/agent-setup.d.ts +116 -0
- package/dist/commands/api-keys.d.ts +86 -23
- package/dist/commands/apps.d.ts +17 -0
- package/dist/commands/auth.d.ts +7 -0
- package/dist/commands/build.d.ts +5 -1
- package/dist/commands/cloud/auth.d.ts +4 -3
- package/dist/commands/cloud/bundle-deploy.d.ts +45 -0
- package/dist/commands/cloud/context.d.ts +44 -5
- package/dist/commands/cloud/cron.d.ts +53 -0
- package/dist/commands/cloud/debug.d.ts +134 -0
- package/dist/commands/cloud/index.d.ts +89 -12
- package/dist/commands/cloud/token-capabilities.d.ts +76 -0
- package/dist/commands/cloud/tokens.d.ts +23 -0
- package/dist/commands/db.d.ts +52 -25
- package/dist/commands/dev.d.ts +181 -7
- package/dist/commands/doctor.d.ts +5 -0
- package/dist/commands/generate_sdk.d.ts +29 -2
- package/dist/commands/init.d.ts +57 -14
- package/dist/commands/schema.d.ts +6 -0
- package/dist/commands/skills.d.ts +126 -7
- package/dist/commands/start.d.ts +29 -0
- package/dist/commands/storage.d.ts +46 -0
- package/dist/consent-BWHaYx5p.js +76 -0
- package/dist/consent-BWHaYx5p.js.map +1 -0
- package/dist/context-BqhRydBg.js +1234 -0
- package/dist/context-BqhRydBg.js.map +1 -0
- package/dist/daemon-DBn-shxX.js +2 -0
- package/dist/{daemon-entry-LTFKtpHy.js → daemon-entry-Da-D3rmH.js} +3 -4
- package/dist/daemon-entry-Da-D3rmH.js.map +1 -0
- package/dist/db-CMpBK3sv.js +1252 -0
- package/dist/db-CMpBK3sv.js.map +1 -0
- package/dist/derive-RPIsAVO4.js +474 -0
- package/dist/derive-RPIsAVO4.js.map +1 -0
- package/dist/dev-C9P5Yp9k.js +1593 -0
- package/dist/dev-C9P5Yp9k.js.map +1 -0
- package/dist/dev-db/atlas-dev-database.d.ts +32 -0
- package/dist/dev-db/daemon-entry.d.ts +13 -0
- package/dist/dev-db/daemon.d.ts +10 -0
- package/dist/dev-db/prepare.d.ts +10 -0
- package/dist/dev-preflight-N1evJL3S.js +2 -0
- package/dist/doctor-DYs-NqFI.js +771 -0
- package/dist/doctor-DYs-NqFI.js.map +1 -0
- package/dist/eject-BT6t37D0.js +335 -0
- package/dist/eject-BT6t37D0.js.map +1 -0
- package/dist/fold-static-C1EtHlfV.js +201 -0
- package/dist/fold-static-C1EtHlfV.js.map +1 -0
- package/dist/fold-static.d.ts +26 -3
- package/dist/function-portability-BIvlvR9q.js +265 -0
- package/dist/function-portability-BIvlvR9q.js.map +1 -0
- package/dist/generate_sdk-UV635zSe.js +477 -0
- package/dist/generate_sdk-UV635zSe.js.map +1 -0
- package/dist/index.es.js +189 -22095
- package/dist/index.es.js.map +1 -1
- package/dist/init-fXeBcxuN.js +1042 -0
- package/dist/init-fXeBcxuN.js.map +1 -0
- package/dist/manifest-DzlC60WX.js +871 -0
- package/dist/manifest-DzlC60WX.js.map +1 -0
- package/dist/manifest.d.ts +41 -1
- package/dist/normalize-imports-CmVdE9iZ.js +100 -0
- package/dist/normalize-imports-CmVdE9iZ.js.map +1 -0
- package/dist/package-manager-DSEOwTqM.js +193 -0
- package/dist/package-manager-DSEOwTqM.js.map +1 -0
- package/dist/path-flags-DUBuI2i1.js +77 -0
- package/dist/path-flags-DUBuI2i1.js.map +1 -0
- package/dist/precompress.d.ts +17 -0
- package/dist/prepare-Btn9Ewbn.js +1052 -0
- package/dist/prepare-Btn9Ewbn.js.map +1 -0
- package/dist/{project-3WCbrrfW.js → project-Wp2zoyeN.js} +34 -184
- package/dist/project-Wp2zoyeN.js.map +1 -0
- package/dist/resolve-CJLkfFV0.js +2 -0
- package/dist/resources-DT1COn_6.js +161 -0
- package/dist/resources-DT1COn_6.js.map +1 -0
- package/dist/schema-BDBWvCHu.js +125 -0
- package/dist/schema-BDBWvCHu.js.map +1 -0
- package/dist/skills-DTFibT1m.js +642 -0
- package/dist/skills-DTFibT1m.js.map +1 -0
- package/dist/start-rU2e3Kp5.js +228 -0
- package/dist/start-rU2e3Kp5.js.map +1 -0
- package/dist/{state-C59Elrnt.js → state-Do7ZJYkF.js} +49 -4
- package/dist/state-Do7ZJYkF.js.map +1 -0
- package/dist/status-BR9oyi-y.js +351 -0
- package/dist/status-BR9oyi-y.js.map +1 -0
- package/dist/storage-DyvrSTH3.js +303 -0
- package/dist/storage-DyvrSTH3.js.map +1 -0
- package/dist/telemetry-CAWNCugs.js +338 -0
- package/dist/telemetry-CAWNCugs.js.map +1 -0
- package/dist/telemetry-DbLqGIBW.js +129 -0
- package/dist/telemetry-DbLqGIBW.js.map +1 -0
- package/dist/unknown-command-DNwDcX3w.js +98 -0
- package/dist/unknown-command-DNwDcX3w.js.map +1 -0
- package/dist/upgrade-D-5-Od96.js +1265 -0
- package/dist/upgrade-D-5-Od96.js.map +1 -0
- package/dist/upgrade.d.ts +14 -2
- package/dist/utils/path-flags.d.ts +18 -0
- package/dist/utils/project.d.ts +16 -0
- package/dist/version-Buyyo1ut.js +58 -0
- package/dist/version-Buyyo1ut.js.map +1 -0
- package/package.json +7 -7
- package/templates/eject/backend/src/env.ts +1 -1
- package/templates/eject/backend/src/index.ts +23 -2
- package/templates/overlays/baas/ai-instructions.md +1 -1
- package/templates/overlays/baas/config/storage.ts +5 -3
- package/templates/template/.cursorrules +1 -1
- package/templates/template/.env.example +10 -6
- package/templates/template/.github/copilot-instructions.md +1 -1
- package/templates/template/.mcp.json +2 -2
- package/templates/template/.windsurfrules +1 -1
- package/templates/template/AGENTS.md +1 -1
- package/templates/template/CLAUDE.md +1 -1
- package/templates/template/README.md +11 -12
- package/templates/template/ai-instructions.md +5 -4
- package/templates/template/backend/functions/hello.ts +8 -0
- package/templates/template/backend/package.json +1 -1
- package/templates/template/config/storage.ts +5 -3
- package/templates/template/docker-compose.yml +6 -6
- package/templates/template/frontend/vite.config.ts +11 -3
- package/templates/template/gitignore +6 -1
- package/templates/template/package.json +4 -3
- package/templates/template/scripts/example.ts +6 -4
- package/dist/branch-pointer-dBiClJ0p.js +0 -111
- package/dist/branch-pointer-dBiClJ0p.js.map +0 -1
- package/dist/constraints-DKGbNfUW.js +0 -48
- package/dist/constraints-DKGbNfUW.js.map +0 -1
- package/dist/daemon-DR9Tk0La.js +0 -355
- package/dist/daemon-DR9Tk0La.js.map +0 -1
- package/dist/daemon-entry-LTFKtpHy.js.map +0 -1
- package/dist/dev-preflight-CNLl4rdo.js +0 -367
- package/dist/dev-preflight-CNLl4rdo.js.map +0 -1
- package/dist/project-3WCbrrfW.js.map +0 -1
- package/dist/resolve-BAeRUBhg.js +0 -74
- package/dist/resolve-BAeRUBhg.js.map +0 -1
- 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
|
|
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`,
|
|
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
|
|
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
|
|
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 {
|
|
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" &&
|
|
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
|
-
|
|
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:
|
|
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 —
|
|
136
|
-
# dropped, narrowed or rewritten,
|
|
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:
|
|
140
|
-
#
|
|
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
|
-
|
|
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
|
|
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
|
|
20
|
-
"schema:generate": "rebase schema generate
|
|
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
|
|
27
|
-
// ./generated/sdk — the dictionary is what
|
|
28
|
-
// property name, so a client built without it
|
|
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
|
|
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"}
|