@cedarjs/pg 0.2.0-alpha.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +300 -230
- package/dist/cli.cjs +99 -43
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +85 -29
- package/dist/cli.mjs.map +1 -1
- package/dist/dev-env.cjs +1 -1
- package/dist/dev-env.mjs +1 -1
- package/dist/index.cjs +9 -8
- package/dist/index.d.cts +80 -9
- package/dist/index.d.mts +80 -9
- package/dist/index.mjs +7 -7
- package/dist/jest-teardown.cjs +1 -1
- package/dist/jest-teardown.mjs +1 -1
- package/dist/jest-template.cjs +7 -2
- package/dist/jest-template.cjs.map +1 -1
- package/dist/jest-template.d.cts +7 -2
- package/dist/jest-template.d.mts +7 -2
- package/dist/jest-template.mjs +7 -2
- package/dist/jest-template.mjs.map +1 -1
- package/dist/jest.cjs +1 -1
- package/dist/jest.mjs +1 -1
- package/dist/{lease-BSBBpaR4.mjs → lease-DS1SX8U_.mjs} +23 -3
- package/dist/lease-DS1SX8U_.mjs.map +1 -0
- package/dist/{lease-Bq9wKwQa.cjs → lease-WlmOnNDi.cjs} +41 -3
- package/dist/lease-WlmOnNDi.cjs.map +1 -0
- package/dist/lifecycle-2BZgSVz2.mjs +1072 -0
- package/dist/lifecycle-2BZgSVz2.mjs.map +1 -0
- package/dist/{lifecycle-DEJ4GgWV.cjs → lifecycle-BCeM96tI.cjs} +517 -117
- package/dist/lifecycle-BCeM96tI.cjs.map +1 -0
- package/dist/{load-dev-env-7UjMHhw7.mjs → load-dev-env-BULkXyen.mjs} +2 -2
- package/dist/{load-dev-env-7UjMHhw7.mjs.map → load-dev-env-BULkXyen.mjs.map} +1 -1
- package/dist/{load-dev-env-cII6N4ZP.cjs → load-dev-env-Bl6Ddv1U.cjs} +2 -2
- package/dist/{load-dev-env-cII6N4ZP.cjs.map → load-dev-env-Bl6Ddv1U.cjs.map} +1 -1
- package/dist/{load-mode-env-B0qO9DZx.cjs → load-mode-env-CX9vd56X.cjs} +2 -2
- package/dist/{load-mode-env-B0qO9DZx.cjs.map → load-mode-env-CX9vd56X.cjs.map} +1 -1
- package/dist/{load-mode-env-C8gy4v9V.mjs → load-mode-env-Ce9ujisN.mjs} +2 -2
- package/dist/{load-mode-env-C8gy4v9V.mjs.map → load-mode-env-Ce9ujisN.mjs.map} +1 -1
- package/dist/{load-test-env-C34wpkIX.mjs → load-test-env-BqtA6V-d.mjs} +2 -2
- package/dist/{load-test-env-C34wpkIX.mjs.map → load-test-env-BqtA6V-d.mjs.map} +1 -1
- package/dist/{load-test-env-CkdUjTpV.cjs → load-test-env-Dcgh6YHV.cjs} +2 -2
- package/dist/{load-test-env-CkdUjTpV.cjs.map → load-test-env-Dcgh6YHV.cjs.map} +1 -1
- package/dist/{naming-C2nGVxPk.d.cts → naming-Df4fz_Fb.d.cts} +3 -2
- package/dist/{naming-C2nGVxPk.d.mts → naming-Df4fz_Fb.d.mts} +3 -2
- package/dist/status-B68SGSKx.mjs +26 -0
- package/dist/status-B68SGSKx.mjs.map +1 -0
- package/dist/status-Z_BYdxzI.cjs +31 -0
- package/dist/status-Z_BYdxzI.cjs.map +1 -0
- package/dist/studio-Bi6SU9jE.mjs +221 -0
- package/dist/studio-Bi6SU9jE.mjs.map +1 -0
- package/dist/studio-CdWIdnxr.cjs +280 -0
- package/dist/studio-CdWIdnxr.cjs.map +1 -0
- package/dist/{template-D0gJ_MS2.cjs → template-Cd77H7Cr.cjs} +21 -18
- package/dist/template-Cd77H7Cr.cjs.map +1 -0
- package/dist/{template-CGw3C1Ob.mjs → template-DlqHL8Go.mjs} +21 -18
- package/dist/template-DlqHL8Go.mjs.map +1 -0
- package/dist/template-mode-BqV3GjXI.d.cts +48 -0
- package/dist/template-mode-BqV3GjXI.d.mts +48 -0
- package/dist/template-mode-DK5cAlv9.mjs +81 -0
- package/dist/template-mode-DK5cAlv9.mjs.map +1 -0
- package/dist/template-mode-DMO5-z7O.cjs +92 -0
- package/dist/template-mode-DMO5-z7O.cjs.map +1 -0
- package/dist/test-env.cjs +1 -1
- package/dist/test-env.mjs +1 -1
- package/dist/vite-plus.cjs +113 -10
- package/dist/vite-plus.cjs.map +1 -1
- package/dist/vite-plus.d.cts +41 -2
- package/dist/vite-plus.d.mts +41 -2
- package/dist/vite-plus.mjs +110 -7
- package/dist/vite-plus.mjs.map +1 -1
- package/dist/vitest-template.cjs +6 -3
- package/dist/vitest-template.cjs.map +1 -1
- package/dist/vitest-template.d.cts +6 -3
- package/dist/vitest-template.d.mts +6 -3
- package/dist/vitest-template.mjs +6 -3
- package/dist/vitest-template.mjs.map +1 -1
- package/dist/vitest.cjs +1 -1
- package/dist/vitest.mjs +1 -1
- package/package.json +11 -7
- package/scripts/autopg-version +1 -1
- package/scripts/ci-install-autopg.sh +7 -2
- package/scripts/postinstall.js +41 -13
- package/dist/constants-Ct8myrEn.cjs +0 -41
- package/dist/constants-Ct8myrEn.cjs.map +0 -1
- package/dist/constants-NLR0U4NR.mjs +0 -24
- package/dist/constants-NLR0U4NR.mjs.map +0 -1
- package/dist/lease-B3TuX92y.d.mts +0 -24
- package/dist/lease-BSBBpaR4.mjs.map +0 -1
- package/dist/lease-Bq9wKwQa.cjs.map +0 -1
- package/dist/lease-t4I9JahV.d.cts +0 -24
- package/dist/lifecycle-BOo6xBjD.mjs +0 -684
- package/dist/lifecycle-BOo6xBjD.mjs.map +0 -1
- package/dist/lifecycle-DEJ4GgWV.cjs.map +0 -1
- package/dist/nx.cjs +0 -44
- package/dist/nx.cjs.map +0 -1
- package/dist/nx.d.cts +0 -15
- package/dist/nx.d.mts +0 -15
- package/dist/nx.mjs +0 -36
- package/dist/nx.mjs.map +0 -1
- package/dist/tasks-Caud9yHr.cjs +0 -67
- package/dist/tasks-Caud9yHr.cjs.map +0 -1
- package/dist/tasks-CjaZRi_G.d.mts +0 -19
- package/dist/tasks-DdQoP9We.d.cts +0 -19
- package/dist/tasks-KherHLac.mjs +0 -38
- package/dist/tasks-KherHLac.mjs.map +0 -1
- package/dist/template-CGw3C1Ob.mjs.map +0 -1
- package/dist/template-D0gJ_MS2.cjs.map +0 -1
- package/dist/template-mode-BkEs9LnY.cjs +0 -91
- package/dist/template-mode-BkEs9LnY.cjs.map +0 -1
- package/dist/template-mode-CcxV_Iju.d.cts +0 -28
- package/dist/template-mode-CcxV_Iju.d.mts +0 -28
- package/dist/template-mode-IjlFOG4O.mjs +0 -80
- package/dist/template-mode-IjlFOG4O.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -1,189 +1,217 @@
|
|
|
1
1
|
# cedar-pg
|
|
2
2
|
|
|
3
|
-
Worktree-isolated local Postgres for
|
|
3
|
+
Worktree-isolated local Postgres for Vite+, Nx, and CedarJS. The host is [autopg](https://github.com/automagik-dev/autopg) (embedded PostgreSQL 18). cedar-pg creates one database and role per git worktree so parallel checkouts do not share a DB.
|
|
4
4
|
|
|
5
|
-
Published
|
|
5
|
+
Published as `@cedarjs/pg`. CLI: `cedarpg`. Pre-1.0 (`0.x`, `latest` dist-tag). Public APIs may still change between minor versions.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
| Layer | Owns |
|
|
8
|
+
| -------- | --------------------------------------------------------------------------------- |
|
|
9
|
+
| autopg | Postgres process, port, concurrent connections |
|
|
10
|
+
| cedar-pg | Per-worktree `CREATE DATABASE` and role, lease files, `DATABASE_URL`, dispose, GC |
|
|
8
11
|
|
|
9
|
-
##
|
|
10
|
-
|
|
11
|
-
autopg runs embedded PostgreSQL 18 (not WASM) with real concurrent connections. No credentials, zero config, and databases are provisioned on first use. Any client works (`psql`, `node-postgres`, Prisma, Drizzle, TypeORM).
|
|
12
|
-
|
|
13
|
-
### Development & testing
|
|
14
|
-
|
|
15
|
-
| Use case | What you get |
|
|
16
|
-
| ----------------------- | ------------------------------------------ |
|
|
17
|
-
| **Local development** | PostgreSQL without Docker |
|
|
18
|
-
| **Integration testing** | Real PostgreSQL, not mocks |
|
|
19
|
-
| **CI/CD pipelines** | Fresh databases per test run |
|
|
20
|
-
| **E2E testing** | Isolated database for Playwright / Cypress |
|
|
12
|
+
## Install
|
|
21
13
|
|
|
22
|
-
|
|
14
|
+
Node `>=24`.
|
|
23
15
|
|
|
24
|
-
|
|
16
|
+
```bash
|
|
17
|
+
npm install -D @cedarjs/pg
|
|
18
|
+
# pnpm add -D @cedarjs/pg
|
|
19
|
+
# yarn add -D @cedarjs/pg
|
|
20
|
+
```
|
|
25
21
|
|
|
26
|
-
|
|
27
|
-
- **dev** DBs persist across restarts; **test** DBs drop on dispose
|
|
28
|
-
- First-class **Vite+ Task** + **Nx** / Vitest / Jest adapters
|
|
29
|
-
- `postinstall` ensures the pinned `autopg` binary when missing
|
|
22
|
+
`postinstall` installs the pinned autopg binary when it is missing. The pin is `scripts/autopg-version`. To bump autopg, change that file.
|
|
30
23
|
|
|
31
|
-
|
|
32
|
-
| ------------ | ------------------------------------------------------------------- |
|
|
33
|
-
| **autopg** | Embedded Postgres host (concurrent, zero-config, auto-provision) |
|
|
34
|
-
| **cedar-pg** | Per-worktree `CREATE DATABASE` / role, `DATABASE_URL`, dispose + GC |
|
|
24
|
+
An autopg that is already installed is never upgraded for you, because upgrading restarts the host every worktree shares. When it is older than the pin, `postinstall` and the first host attach of each process (`acquire`, `cedarpg run`, test setup) print a warning with the upgrade commands. It never fails. `cedarpg run --attach` and skipped acquires (external URL, `CEDAR_PG=0`) do not check. To upgrade, run the install command below, then `autopg update` (autopg's own migrations; on its own it does not download a new binary). `scripts/ci-install-autopg.sh` and the GitHub Action replace any binary that is not exactly the pinned version, since CI hosts are ephemeral.
|
|
35
25
|
|
|
36
|
-
|
|
26
|
+
To install the host by hand (local, non-CI; upstream `install.sh` may use pm2):
|
|
37
27
|
|
|
38
28
|
```bash
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
29
|
+
VER=$(tr -d '[:space:]' < scripts/autopg-version)
|
|
30
|
+
curl -fsSL "https://raw.githubusercontent.com/automagik-dev/autopg/${VER}/install.sh" \
|
|
31
|
+
| AUTOPG_VERSION="$VER" bash
|
|
42
32
|
```
|
|
43
33
|
|
|
44
|
-
|
|
34
|
+
Once per machine, run the host (`autopg daemon`, or your usual install). Then per worktree:
|
|
45
35
|
|
|
46
|
-
```
|
|
47
|
-
|
|
36
|
+
```bash
|
|
37
|
+
cedarpg acquire --mode=dev
|
|
48
38
|
```
|
|
49
39
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
| Name | Meaning |
|
|
53
|
-
| ----------------------------------- | -------------------------- |
|
|
54
|
-
| `cpg_cedar_cedar_dev_a1b2c3d4` | main `cedar` checkout, dev |
|
|
55
|
-
| `cpg_cedar_feat_auth_test_e5f67890` | worktree `feat-auth`, test |
|
|
40
|
+
Connect with the printed `DATABASE_URL`.
|
|
56
41
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
A running [autopg](https://github.com/automagik-dev/autopg) host (installed automatically by `postinstall`, or manually).
|
|
60
|
-
The release pin lives in **`scripts/autopg-version`** (single source of truth for postinstall, CI binary install, and docs). Bump that file to upgrade:
|
|
42
|
+
From another checkout of this repo, pack first, then depend on the build:
|
|
61
43
|
|
|
62
44
|
```bash
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
| AUTOPG_VERSION="$VER" bash
|
|
45
|
+
vp pack
|
|
46
|
+
# in the app: yarn add @cedarjs/pg@file:../cedar-pg
|
|
47
|
+
# or install the tarball vp pack writes (name includes the version in package.json)
|
|
67
48
|
```
|
|
68
49
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
## Develop this package (Vite+)
|
|
50
|
+
Every push to `main` and every PR also builds a preview on [pkg.pr.new](https://pkg.pr.new) (not the npm registry):
|
|
72
51
|
|
|
73
52
|
```bash
|
|
74
|
-
|
|
75
|
-
vp check
|
|
76
|
-
vp test
|
|
77
|
-
vp pack # → dist/ (dts + esm + cjs)
|
|
78
|
-
vp run smoke # build → npm-pack tarball → install + resolve exports
|
|
79
|
-
vp run smoke:pg # pack → Vitest + Jest adapters against real ephemeral Postgres
|
|
53
|
+
pnpm add -D https://pkg.pr.new/@cedarjs/pg@<commit-sha-or-pr-number>
|
|
80
54
|
```
|
|
81
55
|
|
|
82
|
-
##
|
|
56
|
+
## Acquire a database
|
|
83
57
|
|
|
84
|
-
|
|
85
|
-
# in this repo
|
|
86
|
-
vp pack
|
|
58
|
+
`dev` databases persist across restarts. `test` databases drop on `dispose`.
|
|
87
59
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
60
|
+
Names look like this (visible in `psql` `\l`):
|
|
61
|
+
|
|
62
|
+
```text
|
|
63
|
+
cpg_<repo>_<worktree>_<mode>_<pathHash8>
|
|
91
64
|
```
|
|
92
65
|
|
|
66
|
+
Worktree state lives in `.cedarpg`. Import `STATE_DIRNAME` instead of hardcoding that string. `gc` uses `~/.cedarpg/registry`.
|
|
67
|
+
|
|
93
68
|
## CLI
|
|
94
69
|
|
|
95
70
|
```bash
|
|
96
71
|
cedarpg acquire --mode=dev
|
|
97
72
|
cedarpg acquire --mode=test --print-env
|
|
98
|
-
cedarpg run --mode=dev --
|
|
73
|
+
cedarpg run --mode=dev -- prisma migrate deploy
|
|
99
74
|
cedarpg run --mode=test -- vitest run
|
|
75
|
+
cedarpg run --attach --mode=dev -- node dist/server.js # existing lease only, no DDL
|
|
100
76
|
cedarpg dispose --mode=test
|
|
101
77
|
cedarpg print-url --mode=dev
|
|
102
|
-
cedarpg
|
|
78
|
+
cedarpg status --mode=dev
|
|
79
|
+
cedarpg studio --mode=dev # Prisma or Drizzle Studio (--prisma / --drizzle)
|
|
80
|
+
cedarpg gc # drop DBs whose worktree root is gone
|
|
103
81
|
```
|
|
104
82
|
|
|
105
|
-
`
|
|
106
|
-
`TEST_DATABASE_URL` in test mode) in the **child** process, then execs the command.
|
|
107
|
-
Use it for Nx / e2e / API wrappers — local `.env` URLs do not win inside the child.
|
|
83
|
+
`status` and `studio` are read-only. They do not acquire. `studio` walks from cwd up to the worktree.
|
|
108
84
|
|
|
109
|
-
|
|
85
|
+
`cedarpg run` acquires (idempotent role and database DDL), then execs the command. The child always gets `DATABASE_URL` from the lease. In test mode it also gets `TEST_DATABASE_URL`. `--force` only sets `CEDAR_PG_FORCE=1`, so nested adapters do not treat an ambient URL as an escape hatch.
|
|
110
86
|
|
|
111
|
-
|
|
112
|
-
(Vite+ `env: [...]` does). **Canonical fix:** wrap the child with `cedarpg run`.
|
|
113
|
-
Secondary: point Nx `envFile` at `.cedarpg/<mode>.env` after acquire.
|
|
87
|
+
`cedarpg run --attach` skips the acquire. It reads the lease a prior `acquire` / `run` wrote, sets the same child env, and execs. It never runs DDL and never starts or revives the host. It fails (nonzero, before the child starts) when there is no lease or nothing is listening on the leased port.
|
|
114
88
|
|
|
115
|
-
|
|
116
|
-
import { cedarPgNxTargets, cedarPgRunCommand, relativeEnvFile } from "@cedarjs/pg/nx";
|
|
89
|
+
If two targets run `cedarpg acquire` or plain `cedarpg run` on the same worktree, role and database DDL can race. Acquire once, then wrap children with `run --attach`. Any number of those can run concurrently.
|
|
117
90
|
|
|
118
|
-
|
|
119
|
-
// { "db:acquire": { command: "cedarpg acquire --mode=dev", cache: false }, … }
|
|
91
|
+
## Vite+
|
|
120
92
|
|
|
121
|
-
|
|
122
|
-
//
|
|
93
|
+
```ts
|
|
94
|
+
// vite.config.ts
|
|
95
|
+
import { defineConfig } from "vite-plus";
|
|
96
|
+
import { cedarPgTasks, cedarPgDev } from "@cedarjs/pg/vite-plus";
|
|
123
97
|
|
|
124
|
-
|
|
98
|
+
export default defineConfig({
|
|
99
|
+
plugins: [cedarPgDev()],
|
|
100
|
+
run: {
|
|
101
|
+
tasks: {
|
|
102
|
+
...cedarPgTasks(),
|
|
103
|
+
test: {
|
|
104
|
+
command: "vp test",
|
|
105
|
+
dependsOn: ["db:acquire-test"],
|
|
106
|
+
env: ["DATABASE_URL", "TEST_DATABASE_URL"],
|
|
107
|
+
},
|
|
108
|
+
dev: {
|
|
109
|
+
command: "cedarpg run --attach --mode=dev -- vp dev",
|
|
110
|
+
dependsOn: ["db:acquire"],
|
|
111
|
+
cache: false,
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
});
|
|
125
116
|
```
|
|
126
117
|
|
|
127
|
-
|
|
118
|
+
Like Nx `dependsOn`, Vite+ `dependsOn` does not forward env, and task `env` only fingerprints and passes through variables already set in the `vp` process. So `dev` acquires once via `db:acquire`, then `cedarpg run --attach` hands the lease `DATABASE_URL` to `vp dev`. `test` needs no wrapper: the stock Vitest setup acquires and sets the URL itself.
|
|
119
|
+
|
|
120
|
+
`cedarPgDev()` does not acquire. Keep `dependsOn: ["db:acquire"]`. On listen it prints a status panel (TTY, non-CI). Vite CLI shortcuts (`key` then Enter; also listed under `h`):
|
|
121
|
+
|
|
122
|
+
| Key | Action |
|
|
123
|
+
| --- | ---------------------------------------------------------------------- |
|
|
124
|
+
| `d` | Reprint status (name, port, `DATABASE_URL`, env file) |
|
|
125
|
+
| `s` | Open Prisma Studio or Drizzle Kit Studio with the lease `DATABASE_URL` |
|
|
126
|
+
|
|
127
|
+
Options: `cedarPgDev({ mode, root, cwd, studio: "prisma" | "drizzle" | false })`. Studio walks from `cwd` (default Vite `config.root`) up to the worktree, so an Nx `apps/...` package is found without moving the lease. `prisma` wins when both ORMs sit in the same package.
|
|
128
|
+
|
|
129
|
+
Shortcuts bind on Vite 8 and vite-plus. Vite 7 and Cedar print the listen panel only. Use `cedarpg status` and `cedarpg studio` for Nx and other non-Vite hosts.
|
|
130
|
+
|
|
131
|
+
## Nx
|
|
132
|
+
|
|
133
|
+
Nx `dependsOn` does not forward env from one target to the next, so the setup has two parts:
|
|
134
|
+
|
|
135
|
+
1. **One `db:ready` target** acquires the worktree database and migrates it. It is the only target that runs role/database DDL.
|
|
136
|
+
2. **Children** (`dev`, `serve`, workers, e2e) depend on `db:ready` and pick up the lease URL in one of two ways. They never acquire again: two acquires on the same worktree race DDL.
|
|
137
|
+
|
|
138
|
+
```jsonc
|
|
139
|
+
// project.json (workspace root)
|
|
128
140
|
{
|
|
129
141
|
"targets": {
|
|
130
|
-
"
|
|
131
|
-
"command": "cedarpg run --mode=dev --
|
|
142
|
+
"db:ready": {
|
|
143
|
+
"command": "cedarpg run --mode=dev -- prisma migrate deploy",
|
|
144
|
+
"cache": false,
|
|
132
145
|
},
|
|
133
|
-
"db:
|
|
134
|
-
"
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
"options": { "envFile": ".cedarpg/dev.env" }
|
|
138
|
-
}
|
|
139
|
-
}
|
|
146
|
+
"db:status": { "command": "cedarpg status --mode=dev", "cache": false },
|
|
147
|
+
"db:url": { "command": "cedarpg print-url --mode=dev", "cache": false },
|
|
148
|
+
"db:studio": { "command": "cedarpg studio --mode=dev", "cache": false },
|
|
149
|
+
},
|
|
140
150
|
}
|
|
141
151
|
```
|
|
142
152
|
|
|
143
|
-
|
|
153
|
+
Swap in your migrate command (`drizzle-kit migrate`, …). Add `--force` (`cedarpg run --mode=dev --force -- …`) when a shell or `.env` `DATABASE_URL` would otherwise trip the [external-URL escape hatch](#environment-variables). For a migrate step written in TypeScript, call `createAcquireTask` from a script instead:
|
|
144
154
|
|
|
145
155
|
```ts
|
|
146
|
-
// tools/db-ready.ts
|
|
156
|
+
// tools/db-ready.ts → "command": "tsx tools/db-ready.ts"
|
|
147
157
|
import { createAcquireTask } from "@cedarjs/pg";
|
|
148
158
|
|
|
149
159
|
await createAcquireTask({
|
|
150
160
|
mode: "dev",
|
|
161
|
+
force: true,
|
|
151
162
|
afterAcquire: async ({ databaseUrl }) => {
|
|
152
|
-
//
|
|
163
|
+
// run your migrations against databaseUrl
|
|
153
164
|
},
|
|
154
165
|
})();
|
|
155
166
|
```
|
|
156
167
|
|
|
157
|
-
|
|
158
|
-
`import "@cedarjs/pg/dev-env"`. Absolute path helper: `envFilePath(root, mode)`.
|
|
168
|
+
`db:status`, `db:url`, and `db:studio` are optional local helpers. They read the lease and never acquire. `studio` opens Prisma Studio or Drizzle Kit Studio, whichever it finds.
|
|
159
169
|
|
|
160
|
-
|
|
170
|
+
### Children: preload or attach
|
|
161
171
|
|
|
162
|
-
|
|
163
|
-
// vite.config.ts
|
|
164
|
-
import { defineConfig } from "vite-plus";
|
|
165
|
-
import { cedarPgTasks } from "@cedarjs/pg/vite-plus";
|
|
172
|
+
Pick one per target. Both give the child the `DATABASE_URL` that `db:ready` leased, overriding a `DATABASE_URL` from `.env`.
|
|
166
173
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
174
|
+
```jsonc
|
|
175
|
+
{
|
|
176
|
+
"targets": {
|
|
177
|
+
// Preload: Node loads .cedarpg/dev.env with overwrite before your code runs.
|
|
178
|
+
"serve": {
|
|
179
|
+
"dependsOn": ["db:ready"],
|
|
180
|
+
"command": "node --require @cedarjs/pg/dev-env dist/server.js",
|
|
181
|
+
},
|
|
182
|
+
"dev": {
|
|
183
|
+
"dependsOn": ["db:ready"],
|
|
184
|
+
"executor": "nx:run-commands",
|
|
185
|
+
"options": {
|
|
186
|
+
"command": "tsx watch src/server.ts",
|
|
187
|
+
"env": { "NODE_OPTIONS": "--require @cedarjs/pg/dev-env" },
|
|
180
188
|
},
|
|
181
189
|
},
|
|
190
|
+
// Attach: cedarpg sets the env, then execs any command (Node or not).
|
|
191
|
+
"worker": {
|
|
192
|
+
"dependsOn": ["db:ready"],
|
|
193
|
+
"command": "cedarpg run --attach --mode=dev -- node dist/worker.js",
|
|
194
|
+
},
|
|
182
195
|
},
|
|
183
|
-
}
|
|
196
|
+
}
|
|
184
197
|
```
|
|
185
198
|
|
|
186
|
-
|
|
199
|
+
- **Preload** (`@cedarjs/pg/dev-env`, or `loadDevEnv({ overwrite: true })` at the top of your entry) costs nothing extra but only works for Node processes. With no lease it is a no-op, and the process keeps its ambient URL.
|
|
200
|
+
- **Attach** (`cedarpg run --attach --mode=dev -- <cmd>`) works for any command. It reads the lease and checks the port, and fails before the child starts when either is missing. It never runs DDL and never starts the host, so any number of attached children can start at once.
|
|
201
|
+
|
|
202
|
+
Avoid Nx `envFile: ".cedarpg/dev.env"`. An ambient `.env` `DATABASE_URL` wins over it.
|
|
203
|
+
|
|
204
|
+
### Tests
|
|
205
|
+
|
|
206
|
+
Tests do not go through `db:ready`. The Jest TEMPLATE adapter acquires its own `test` lease, migrates once, clones per worker, and drops everything in teardown. Point Jest at it (see [TEMPLATE clones → Jest](#jest)) and make the Nx `test` target a plain runner command:
|
|
207
|
+
|
|
208
|
+
```jsonc
|
|
209
|
+
{ "targets": { "test": { "command": "jest --config jest.config.cjs" } } }
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Vitest and Jest
|
|
213
|
+
|
|
214
|
+
Stock `@cedarjs/pg/vitest` and `@cedarjs/pg/jest` only acquire and dispose. One shared test DB. They do not migrate, and they do not clone per worker. For migrate-once plus clones, see [TEMPLATE clones](#template-clones).
|
|
187
215
|
|
|
188
216
|
```ts
|
|
189
217
|
// vitest.config.ts
|
|
@@ -196,142 +224,87 @@ export default defineConfig({
|
|
|
196
224
|
});
|
|
197
225
|
```
|
|
198
226
|
|
|
227
|
+
Vitest runs `globalSetup` in the main process, then workers inherit `process.env`. Add `setupFiles: ["@cedarjs/pg/test-env"]` only if your pool does not inherit env.
|
|
228
|
+
|
|
199
229
|
```js
|
|
200
|
-
// jest.config.cjs
|
|
230
|
+
// jest.config.cjs
|
|
201
231
|
module.exports = {
|
|
202
232
|
globalSetup: require.resolve("@cedarjs/pg/jest"),
|
|
203
233
|
globalTeardown: require.resolve("@cedarjs/pg/jest-teardown"),
|
|
204
|
-
// Jest globalSetup is a separate process
|
|
234
|
+
// Jest globalSetup is a separate process. Workers load DATABASE_URL from .cedarpg/test.env.
|
|
205
235
|
setupFiles: [require.resolve("@cedarjs/pg/test-env")],
|
|
206
236
|
};
|
|
207
237
|
```
|
|
208
238
|
|
|
209
|
-
|
|
239
|
+
`loadTestEnv` and `loadDevEnv` fill undefined keys by default. Pass `{ overwrite: true }` (or import `@cedarjs/pg/dev-env`) when a local `.env` URL should lose to cedar-pg. That is not the same as `CEDAR_PG_FORCE` or `{ force: true }` (external-URL escape hatch).
|
|
240
|
+
|
|
241
|
+
## CedarJS and custom globalSetup
|
|
210
242
|
|
|
211
|
-
If
|
|
243
|
+
If the runner already owns `globalSetup` (Prisma push or migrate after acquire), do not replace it with `@cedarjs/pg/jest`. Compose:
|
|
212
244
|
|
|
213
|
-
1. In
|
|
214
|
-
2. Add `setupFiles: [require.resolve(
|
|
215
|
-
3. In
|
|
245
|
+
1. In `globalSetup`, call `acquireIfNeeded` when opted in, then migrate.
|
|
246
|
+
2. Add `setupFiles: [require.resolve("@cedarjs/pg/test-env")]` so Jest workers see `DATABASE_URL`.
|
|
247
|
+
3. In `globalTeardown`, call `dispose({ mode: "test", root })`.
|
|
216
248
|
|
|
217
249
|
```ts
|
|
218
|
-
// framework globalSetup (sketch)
|
|
219
250
|
import { acquireIfNeeded } from "@cedarjs/pg";
|
|
220
251
|
|
|
221
252
|
if (process.env.CEDAR_PG === "1" || process.env.CEDAR_PG === "true") {
|
|
222
253
|
await acquireIfNeeded({
|
|
223
254
|
root: projectRoot, // e.g. getPaths().base
|
|
224
255
|
mode: "test",
|
|
225
|
-
setEnv: true, // this process (prisma)
|
|
256
|
+
setEnv: true, // this process (prisma). Workers use @cedarjs/pg/test-env.
|
|
226
257
|
url: process.env.TEST_DATABASE_URL,
|
|
227
258
|
force: process.env.CEDAR_PG_FORCE === "1",
|
|
228
|
-
disabled: false, // framework opt-in
|
|
259
|
+
disabled: false, // framework opt-in. Stock adapters use CEDAR_PG=0 opt-out.
|
|
229
260
|
});
|
|
230
261
|
}
|
|
231
|
-
// … prisma db push / migrate …
|
|
232
262
|
```
|
|
233
263
|
|
|
234
|
-
|
|
235
|
-
// jest-preset
|
|
236
|
-
setupFiles: [require.resolve("@cedarjs/pg/test-env")],
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
Use exported `STATE_DIRNAME` (`.cedarpg`) / `loadTestEnv` / `loadDevEnv` /
|
|
240
|
-
`envFilePath(root, mode)` instead of hardcoding the lease dir.
|
|
241
|
-
|
|
242
|
-
`loadTestEnv` / `loadDevEnv` only fill **undefined** keys by default. Pass
|
|
243
|
-
`{ overwrite: true }` (or import `@cedarjs/pg/dev-env`) when a local `.env`
|
|
244
|
-
`DATABASE_URL` / `TEST_DATABASE_URL` should lose to cedar-pg. That is not the
|
|
245
|
-
same as `CEDAR_PG_FORCE` / acquire `{ force }` (external-URL escape hatch).
|
|
246
|
-
|
|
247
|
-
## Programmatic API
|
|
248
|
-
|
|
249
|
-
```ts
|
|
250
|
-
import { acquire, dispose, loadTestEnv, loadDevEnv, envFilePath, STATE_DIRNAME } from "@cedarjs/pg";
|
|
251
|
-
|
|
252
|
-
const { databaseUrl, adminUrl, databaseName, dispose: drop } = await acquire({ mode: "test" });
|
|
253
|
-
// … tests …
|
|
254
|
-
await drop();
|
|
255
|
-
|
|
256
|
-
loadDevEnv({ overwrite: true }); // override .env DATABASE_URL from .cedarpg/dev.env
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
### Host startup (CI ephemeral)
|
|
260
|
-
|
|
261
|
-
By default cedar-pg **attaches** to a live autopg host (`autopg status`). If none is live it runs bare `autopg install` (pm2) — fine for local machines, hostile to GitHub Actions (no pm2) and slower than RAM-backed CI.
|
|
262
|
-
|
|
263
|
-
In CI, cedar-pg starts an **opinionated ephemeral host** automatically when `CI=true` (or when forced). Callers just use `acquire` — no host options bag:
|
|
264
|
-
|
|
265
|
-
```ts
|
|
266
|
-
import { acquire } from "@cedarjs/pg";
|
|
267
|
-
|
|
268
|
-
// CI=true → install --no-pm2 --no-ui + detached postmaster (--ram on Linux /dev/shm)
|
|
269
|
-
const { databaseUrl } = await acquire({ mode: "test" });
|
|
270
|
-
```
|
|
271
|
-
|
|
272
|
-
| Signal | Effect |
|
|
273
|
-
| --------------------------- | ------------------------------------------------------------------- |
|
|
274
|
-
| `CEDAR_PG_EPHEMERAL_HOST=1` | Prefer ephemeral start when **no** host is live (attach still wins) |
|
|
275
|
-
| `CEDAR_PG_EPHEMERAL_HOST=0` | Force local attach / pm2 install (even if `CI=true`) |
|
|
276
|
-
| unset + `CI=true` | Prefer ephemeral when no host is live |
|
|
277
|
-
| otherwise | Local: attach if live, else bare `autopg install` |
|
|
278
|
-
|
|
279
|
-
Ephemeral recipe (not configurable via cedar-pg):
|
|
280
|
-
|
|
281
|
-
- `autopg install --no-pm2 --no-ui --port 55432 --data DIR`
|
|
282
|
-
- detached `autopg postmaster --port 55432 --socket-dir DIR --data DIR`
|
|
283
|
-
- Linux when `/dev/shm` exists → also `--ram` and `DIR=/dev/shm/cedar-pg-<uid>`
|
|
284
|
-
- otherwise → disk `DIR` under the OS temp dir (still owned, no pm2)
|
|
285
|
-
- Ready when TCP accepts on the recipe port (not merely `autopg status` after install)
|
|
286
|
-
|
|
287
|
-
If a host is already live, cedar-pg attaches and does not start another. The **CI job owns** ephemeral postmaster lifetime (runner teardown / `/dev/shm`); there is no cedar-pg host dispose API.
|
|
264
|
+
## TEMPLATE clones
|
|
288
265
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
Prefer the composite action (cache + attested binary install, no pm2). Version defaults to this repo’s `scripts/autopg-version`:
|
|
292
|
-
|
|
293
|
-
```yaml
|
|
294
|
-
- uses: actions/checkout@v6
|
|
295
|
-
# In cedar-pg:
|
|
296
|
-
- uses: ./.github/actions/setup-autopg
|
|
297
|
-
# From another repo (pin to a tag when publishing the action):
|
|
298
|
-
# - uses: cedarjs/cedar-pg/.github/actions/setup-autopg@main
|
|
299
|
-
```
|
|
266
|
+
Migrate stays app-owned via `createGlobalSetup({ migrate })`. The adapter then marks TEMPLATE and clones per worker.
|
|
300
267
|
|
|
301
|
-
|
|
268
|
+
Point `globalSetup` at a local module that calls `createGlobalSetup`. String-resolving the package entry without a migrate hook throws.
|
|
302
269
|
|
|
303
|
-
|
|
270
|
+
Each setup starts clean. Before migrate, `createGlobalSetup` drops every database owned by this worktree's test role: a TEMPLATE and worker clones left behind by a crashed or killed run, even if its lease file is gone. `migrate` therefore always runs against an empty database, and you do not need your own pre-cleanup. Only this worktree's `cpg_*_test_*` role is touched, never other databases on the shared host. Core API: `acquire({ mode: "test", fresh: true })`.
|
|
304
271
|
|
|
305
|
-
|
|
272
|
+
`cloneWorkerDatabase()` gives each worker one clone, `<template>_c_<JEST_WORKER_ID | VITEST_POOL_ID | pid>`, shared by every test file that worker runs. The first file creates it. Later files find it in Postgres and reuse it. Nothing is cached in memory, so this holds under Jest's per-file `globalThis` and module registry. If the clone name exists but belongs to a different role, the call fails with a clear error rather than reusing it.
|
|
306
273
|
|
|
307
|
-
|
|
274
|
+
Each file then starts from empty tables. By default `cloneWorkerDatabase()` runs one `TRUNCATE ... RESTART IDENTITY` over every user table in the clone, so rows from earlier files in the worker are gone and `serial` / identity IDs restart at 1. Tests that expect a first row with `id = 1` behave the same whichever file a worker runs first. It truncates on every call, the first file included, so rows that `migrate` seeded into the TEMPLATE (including bookkeeping tables such as `_prisma_migrations`) are cleared too. It skips system schemas, temp tables, and tables an extension owns (for example PostGIS `spatial_ref_sys`). If skip policy uses an external URL or `CEDAR_PG=0` is set, nothing is cloned and nothing is truncated.
|
|
308
275
|
|
|
309
|
-
|
|
276
|
+
To keep the clone as the previous file left it and reset on your own, pass `cloneWorkerDatabase({ reset: "none" })`. Resets between tests inside one file stay app-owned.
|
|
310
277
|
|
|
311
|
-
|
|
278
|
+
### Jest
|
|
312
279
|
|
|
313
280
|
```js
|
|
314
281
|
// jest.cedar-global.cjs
|
|
315
282
|
const { createGlobalSetup } = require("@cedarjs/pg/jest/template");
|
|
316
283
|
module.exports = createGlobalSetup({
|
|
317
284
|
migrate: async ({ databaseUrl }) => {
|
|
318
|
-
// prisma migrate reset
|
|
285
|
+
// prisma migrate reset, drizzle push
|
|
319
286
|
},
|
|
320
287
|
});
|
|
321
288
|
|
|
322
289
|
// jest.config.cjs
|
|
290
|
+
// When .env has a real TEST_DATABASE_URL, set FORCE once here so it
|
|
291
|
+
// inherits into globalSetup and workers (dotenv will not override existing keys).
|
|
292
|
+
process.env.CEDAR_PG_FORCE = "1";
|
|
293
|
+
|
|
323
294
|
module.exports = {
|
|
324
295
|
globalSetup: "<rootDir>/jest.cedar-global.cjs",
|
|
325
296
|
globalTeardown: require.resolve("@cedarjs/pg/jest-teardown"),
|
|
297
|
+
// Runs once per test file; every file in a worker reuses that worker's clone,
|
|
298
|
+
// truncated with RESTART IDENTITY first.
|
|
326
299
|
setupFilesAfterEnv: ["<rootDir>/jest.cedar-worker.cjs"],
|
|
327
300
|
};
|
|
328
301
|
|
|
329
|
-
// jest.cedar-worker.cjs
|
|
302
|
+
// jest.cedar-worker.cjs
|
|
330
303
|
const { cloneWorkerDatabase } = require("@cedarjs/pg/jest/template");
|
|
331
304
|
beforeAll(() => cloneWorkerDatabase());
|
|
332
305
|
```
|
|
333
306
|
|
|
334
|
-
|
|
307
|
+
### Vitest
|
|
335
308
|
|
|
336
309
|
```ts
|
|
337
310
|
// vitest.cedar-global.ts
|
|
@@ -350,12 +323,14 @@ export default defineConfig({
|
|
|
350
323
|
},
|
|
351
324
|
});
|
|
352
325
|
|
|
353
|
-
// vitest.cedar-worker.ts
|
|
326
|
+
// vitest.cedar-worker.ts
|
|
354
327
|
import { cloneWorkerDatabase } from "@cedarjs/pg/vitest/template";
|
|
355
328
|
await cloneWorkerDatabase();
|
|
356
329
|
```
|
|
357
330
|
|
|
358
|
-
|
|
331
|
+
### Core API
|
|
332
|
+
|
|
333
|
+
No runner adapters.
|
|
359
334
|
|
|
360
335
|
```ts
|
|
361
336
|
import { acquire, markTemplate, cloneFromTemplate, dispose } from "@cedarjs/pg";
|
|
@@ -369,36 +344,131 @@ const worker = await cloneFromTemplate({
|
|
|
369
344
|
name: "1",
|
|
370
345
|
setEnv: true,
|
|
371
346
|
});
|
|
372
|
-
// … tests …
|
|
373
347
|
await worker.dropClone(); // optional: drop one clone only
|
|
374
|
-
await dispose({ root: acquired.root, mode: "test" }); //
|
|
348
|
+
await dispose({ root: acquired.root, mode: "test" }); // TEMPLATE + all clones + role
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
`acquire` returns `adminUrl` for migrate hooks and privileged DDL. `markTemplate` and `cloneFromTemplate` accept it, or rediscover the host when it is omitted. `cloneFromTemplate` uses the admin connection (`CREATE DATABASE ... TEMPLATE`). Test roles stay `LOGIN`-only.
|
|
352
|
+
|
|
353
|
+
`setEnv` defaults to false on `cloneFromTemplate`. It defaults to true on `cloneFromTemplateIfNeeded` (same as `acquireIfNeeded`). Worker adapters call `cloneFromTemplateIfNeeded` via `cloneWorkerDatabase`, with `reuse: true`.
|
|
354
|
+
|
|
355
|
+
An explicit `name` that already exists fails with `database already exists` unless you pass `reuse: true`. Then a clone owned by the lease role is kept as is.
|
|
356
|
+
|
|
357
|
+
`dispose` is role-scoped suite teardown, not `dropClone`. It unsets `IS_TEMPLATE` and drops every database owned by the lease role.
|
|
358
|
+
|
|
359
|
+
## Host and CI
|
|
360
|
+
|
|
361
|
+
`acquire` attaches when TCP accepts on the port from `autopg status --json`. Registration is not liveness. An installed-but-stopped host still reports a port.
|
|
362
|
+
|
|
363
|
+
When nothing is listening, cedar-pg brings up the registered local host. It runs `autopg restart`, then (if still dark) detached `autopg postmaster` on the registered port and `~/.autopg/data`. `autopg restart` (autopg ≥ v3.2) drives pm2 only and exits 0 once ready; without pm2, or under systemd-user / launchd, it exits 1 and cedar-pg moves on. Either way TCP accept is the gate. It does not run `autopg install` on an already-registered host (that path wants pm2). `install` is only for a never-registered machine. Same port, same data dir — not a second Postgres. The data dir and socket dir come from `autopg status --json`. They are never guessed: if autopg reports no `dataDir`, revive is skipped. If a live `postmaster.pid` already owns the data dir (pm2 still recovering, or a concurrent acquire), cedar-pg waits for that postmaster instead of starting a competitor. The revived postmaster has no supervisor and lives until reboot or crash. Its output is appended to `cedarpg-postmaster.log` in autopg's `logsDir` (`~/.autopg/logs`). If revive still produces no listener, `acquire` fails with what it tried.
|
|
364
|
+
|
|
365
|
+
Callers use `acquire` (and `adminUrl`). There is no public host-options object. Ephemeral behavior is env-driven.
|
|
366
|
+
|
|
367
|
+
```ts
|
|
368
|
+
import { acquire } from "@cedarjs/pg";
|
|
369
|
+
|
|
370
|
+
// CI=true: detached postmaster (--ram on Linux /dev/shm). Does not rewrite ~/.autopg.
|
|
371
|
+
const { databaseUrl } = await acquire({ mode: "test" });
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
| Signal | Effect (attach always wins when something is listening) |
|
|
375
|
+
| --------------------------- | -------------------------------------------------------------------------------------------- |
|
|
376
|
+
| `CEDAR_PG_EPHEMERAL_HOST=1` | Ephemeral owned postmaster on 55432 |
|
|
377
|
+
| `CEDAR_PG_EPHEMERAL_HOST=0` | Never start the ephemeral 55432 postmaster (even in CI). Local registered host only, or fail |
|
|
378
|
+
| unset and `CI=true` | Ephemeral |
|
|
379
|
+
| unset | Local: `autopg restart`, then registered `postmaster` if still dark, then fail |
|
|
380
|
+
|
|
381
|
+
Ephemeral recipe (not configurable via cedar-pg):
|
|
382
|
+
|
|
383
|
+
- Detached `autopg postmaster --port 55432 --socket-dir DIR --data DIR`
|
|
384
|
+
- Does not run `autopg install` (that rewrites `~/.autopg/admin.json` and conflicts with a local pm2 host)
|
|
385
|
+
- Linux when `/dev/shm` exists: also `--ram` and `DIR=/dev/shm/cedar-pg-<uid>`
|
|
386
|
+
- Otherwise: disk `DIR` under the OS temp dir
|
|
387
|
+
- Ready when TCP accepts on the recipe port
|
|
388
|
+
- Before cold-start, if the recipe port is not live, cedar-pg removes its own leftover data dir (`/dev/shm/cedar-pg-<uid>`, or `$TMPDIR/cedar-pg-host` without `--ram`) from an OOM-killed run. It never touches other `/dev/shm` entries: `PostgreSQL.*` segments belong to every running Postgres, the registered host included.
|
|
389
|
+
|
|
390
|
+
If TCP already accepts on the discovered autopg port, or in ephemeral mode on 55432, cedar-pg attaches and does not start another. The CI job owns ephemeral postmaster lifetime (runner teardown, `/dev/shm`). There is no cedar-pg host dispose API.
|
|
391
|
+
|
|
392
|
+
Cloud VMs often ship `/dev/shm` at about 64MB, too small for `--ram`. Remount before tests if needed (`sudo mount -o remount,size=6G /dev/shm`). See [Troubleshooting](#troubleshooting).
|
|
393
|
+
|
|
394
|
+
### GitHub Actions
|
|
395
|
+
|
|
396
|
+
Prefer the composite action (cache plus attested binary install, no pm2). Version defaults to this repo's `scripts/autopg-version`. Inputs and outputs: [`.github/actions/setup-autopg`](.github/actions/setup-autopg/README.md).
|
|
397
|
+
|
|
398
|
+
```yaml
|
|
399
|
+
- uses: actions/checkout@v6
|
|
400
|
+
# In cedar-pg:
|
|
401
|
+
- uses: ./.github/actions/setup-autopg
|
|
402
|
+
# From another repo (pin to a tag when publishing the action):
|
|
403
|
+
# - uses: cedarjs/cedar-pg/.github/actions/setup-autopg@main
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
The action runs `scripts/ci-install-autopg.sh`. For published-package consumers under `CI=true` without the Action, set `CEDAR_PG_INSTALL_AUTOPG=1` so `postinstall` runs that script (not upstream `install.sh`). That flag is not enough when the package manager disables lifecycle scripts (`--ignore-scripts`, `YARN_ENABLE_SCRIPTS=false`). Prefer the Action, or bake the binary into the image.
|
|
407
|
+
|
|
408
|
+
Yarn Berry or ignore-scripts, when you cannot use the Action. Requires a real `node_modules` tree (`nodeLinker: node-modules` or pnpm). Default Yarn PnP has no `node_modules/@cedarjs/pg/...` path. Resolve via `yarn node` or `require.resolve`, or prefer the Action.
|
|
409
|
+
|
|
410
|
+
```yaml
|
|
411
|
+
- name: Ensure autopg binary
|
|
412
|
+
run: |
|
|
413
|
+
set -euo pipefail
|
|
414
|
+
echo "${HOME}/.local/bin" >> "${GITHUB_PATH}"
|
|
415
|
+
export PATH="${HOME}/.local/bin:${PATH}"
|
|
416
|
+
bash node_modules/@cedarjs/pg/scripts/ci-install-autopg.sh
|
|
417
|
+
env:
|
|
418
|
+
GH_TOKEN: ${{ github.token }}
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
## Environment variables
|
|
422
|
+
|
|
423
|
+
| Var | Meaning |
|
|
424
|
+
| -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
425
|
+
| `AUTOPG_BIN` | Path to the autopg binary |
|
|
426
|
+
| `AUTOPG_PG_USER`, `AUTOPG_PG_PASSWORD` | Autopg superuser for admin URL (default user `postgres`, password `postgres`) |
|
|
427
|
+
| `CEDAR_PG=0` | Disable auto-acquire in adapters |
|
|
428
|
+
| `TEST_DATABASE_URL` | Default escape hatch for `acquireIfNeeded`. Skip acquire for a real external DB (not `cpg_*`, `file:`, or `{...}` or `<...>` placeholders). Callers may pass `url` (`DATABASE_URL` is common in dev) |
|
|
429
|
+
| `CEDAR_PG_FORCE=1` | Ignore the external-URL escape hatch (adapters, `cedarpg run --force`) |
|
|
430
|
+
| `CEDAR_PG_EPHEMERAL_HOST` | `1` owned postmaster. `0` never own one (even in CI). Unset and `CI=true` means ephemeral |
|
|
431
|
+
| `CEDAR_PG_REGISTRY_DIR` | Override the global lease registry (for `gc`) |
|
|
432
|
+
| `CEDAR_PG_SKIP_POSTINSTALL=1` | Skip the autopg install hook |
|
|
433
|
+
| `CEDAR_PG_INSTALL_AUTOPG=1` | Under `CI=true`, run binary-only `ci-install-autopg.sh` from postinstall |
|
|
434
|
+
|
|
435
|
+
`force`, `overwrite`, and `run` are different knobs. Do not collapse them.
|
|
436
|
+
|
|
437
|
+
## Programmatic API
|
|
438
|
+
|
|
439
|
+
```ts
|
|
440
|
+
import { acquire, loadDevEnv } from "@cedarjs/pg";
|
|
441
|
+
|
|
442
|
+
const { databaseUrl, adminUrl, databaseName, dispose } = await acquire({ mode: "test" });
|
|
443
|
+
await dispose();
|
|
444
|
+
|
|
445
|
+
loadDevEnv({ overwrite: true }); // override .env DATABASE_URL from .cedarpg/dev.env
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
Unit tests in this repo do not start Postgres. CI runs `vp run smoke:pg` for Vitest and Jest adapters against real Postgres (ephemeral cold-start when the runner has no live host; attach wins otherwise).
|
|
449
|
+
|
|
450
|
+
## Develop this package
|
|
451
|
+
|
|
452
|
+
Vite+, Node `>=24` (`.node-version`), `pnpm@11`. Contributor rules: [AGENTS.md](AGENTS.md). User-visible API changes: [CHANGELOG.md](CHANGELOG.md).
|
|
453
|
+
|
|
454
|
+
```bash
|
|
455
|
+
vp install
|
|
456
|
+
vp check
|
|
457
|
+
vp test
|
|
458
|
+
vp pack # dist/ (dts + esm + cjs)
|
|
459
|
+
vp run smoke # pack, then tarball install, then resolve exports
|
|
460
|
+
vp run smoke:pg # pack, then Vitest and Jest adapters against real ephemeral Postgres
|
|
375
461
|
```
|
|
376
462
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
|
385
|
-
|
|
|
386
|
-
| `
|
|
387
|
-
| `
|
|
388
|
-
| `
|
|
389
|
-
| `TEST_DATABASE_URL` | Escape hatch: skip acquire for real external DBs (not `cpg_*` / `file:` / `{…}` / `<…>` template placeholders) |
|
|
390
|
-
| `CEDAR_PG_FORCE=1` | Ignore external-URL escape hatch (adapters + `cedarpg acquire --force` / `run --force`) |
|
|
391
|
-
| `CEDAR_PG_EPHEMERAL_HOST` | `1` force / `0` disable ephemeral host (auto when `CI=true`) |
|
|
392
|
-
| `CEDAR_PG_REGISTRY_DIR` | Override global lease registry (for `gc`) |
|
|
393
|
-
| `CEDAR_PG_SKIP_POSTINSTALL=1` | Skip autopg install hook |
|
|
394
|
-
| `CEDAR_PG_INSTALL_AUTOPG=1` | Under `CI=true`, run binary-only `ci-install-autopg.sh` from postinstall |
|
|
395
|
-
|
|
396
|
-
## Alpha caveats
|
|
397
|
-
|
|
398
|
-
- Public API may change before `0.1.0`.
|
|
399
|
-
- End-to-end Postgres flows assume a working local `autopg` host; unit tests do not start Postgres.
|
|
400
|
-
CI runs `vp run smoke:pg` for Vitest/Jest adapters against real Postgres
|
|
401
|
-
(ephemeral cold-start when the runner has no live host; attach-wins otherwise).
|
|
402
|
-
- State lives in product-owned `.cedarpg` (worktree + `~/.cedarpg/registry`), not under autopg's `~/.autopg/` or a generic `.pg`.
|
|
403
|
-
- Role passwords are derived from `roleName` (`cedar-pg\\0` + roleName, scheme v2) so TEMPLATE clones that reuse a role keep working; bump the scheme id to change the derivation.
|
|
404
|
-
- Test TEMPLATE flow: `acquire` → app migrate → `markTemplate` → `cloneFromTemplate` → role-scoped `dispose`. Optional `@cedarjs/pg/jest/template` + `@cedarjs/pg/vitest/template` adapters orchestrate that pipeline via `createGlobalSetup({ migrate })`; migrate stays app-owned.
|
|
463
|
+
## Troubleshooting
|
|
464
|
+
|
|
465
|
+
| Symptom | Fix |
|
|
466
|
+
| --------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
467
|
+
| `ECONNREFUSED 127.0.0.1:25432` on `cedarpg acquire` | autopg is registered but not listening. cedar-pg attaches only after TCP accepts. It runs `autopg restart`, then detached `autopg postmaster` on the registered port/data if still dark — including when pm2 is missing and `restart` exits 1. A revived postmaster logs to `~/.autopg/logs/cedarpg-postmaster.log`. `status=stopped` with `runtime.live=true` is healthy once TCP accepts; cedar-pg will not `install` pm2. If revive fails, the error lists what was tried. |
|
|
468
|
+
| `database already exists: ..._c_<workerId>` in Jest | Upgrade `@cedarjs/pg`. Older releases cached the clone on `globalThis`, which Jest resets for every test file. `cloneWorkerDatabase` now reuses the worker's clone from Postgres (`reuse: true`), and template setup drops crashed-run leftovers first. If the error says `owned by <other role>`, a database outside this worktree's lease has that name: drop it or pass another `name`. |
|
|
469
|
+
| Acquire skipped. Tests hit shared or stale Postgres | A real `.env` `TEST_DATABASE_URL` (or a `url` the caller passed) trips the escape hatch. Set `CEDAR_PG_FORCE=1` once in `jest.config.js`, or `force: true`, or `cedarpg run --force`. |
|
|
470
|
+
| `Disk quota exceeded` / `No space left on device` / Postgres `53100` on ephemeral start | Enlarge `/dev/shm` (`sudo mount -o remount,size=6G /dev/shm`). Cold-start already removes cedar-pg's own leftover `/dev/shm/cedar-pg-<uid>` when the recipe port is dead. Do not `rm /dev/shm/PostgreSQL.*`: a live Postgres (your registered host included) then fails every new connection with `58P01 could not open shared memory segment`, and only a restart of that host recovers it. |
|
|
471
|
+
| `autopg: command not found` in CI with Yarn `YARN_ENABLE_SCRIPTS=false` | `CEDAR_PG_INSTALL_AUTOPG=1` is not enough when lifecycle scripts are off. With `nodeLinker: node-modules`, run `bash node_modules/@cedarjs/pg/scripts/ci-install-autopg.sh` and put `~/.local/bin` on `PATH` (or use `setup-autopg`). PnP: resolve the script path via Yarn, or prefer the Action. |
|
|
472
|
+
| Nx child still uses `.env` `DATABASE_URL` | `dependsOn` does not forward acquire env. Preload `@cedarjs/pg/dev-env` (`node --require` / `NODE_OPTIONS`), or wrap with `cedarpg run --attach --mode=dev -- <cmd>`. Not Nx `envFile`. |
|
|
473
|
+
| Role or DB errors under parallel Nx targets | Children are running plain `cedarpg run` (or `acquire`), so each one runs DDL. Keep the acquire in one `db:ready`, and switch the children to `cedarpg run --attach`. |
|
|
474
|
+
| `no dev lease ...; attach never acquires` from `cedarpg run --attach` | Nothing has acquired this worktree yet. Make the target `dependsOn` your `db:ready` (or run `cedarpg acquire --mode=dev`). On `nothing is listening`, re-run `db:ready` / `acquire` to bring the host back; attach never starts it. |
|