@cedarjs/pg 0.1.0-alpha.1 → 0.2.0-beta.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.
Files changed (129) hide show
  1. package/README.md +361 -89
  2. package/dist/cli.cjs +131 -35
  3. package/dist/cli.cjs.map +1 -1
  4. package/dist/cli.mjs +118 -22
  5. package/dist/cli.mjs.map +1 -1
  6. package/dist/dev-env.cjs +12 -0
  7. package/dist/dev-env.cjs.map +1 -0
  8. package/dist/dev-env.d.cts +1 -0
  9. package/dist/dev-env.d.mts +1 -0
  10. package/dist/dev-env.mjs +14 -0
  11. package/dist/dev-env.mjs.map +1 -0
  12. package/dist/index.cjs +51 -10
  13. package/dist/index.cjs.map +1 -0
  14. package/dist/index.d.cts +198 -84
  15. package/dist/index.d.mts +198 -84
  16. package/dist/index.mjs +32 -2
  17. package/dist/index.mjs.map +1 -0
  18. package/dist/jest-teardown.cjs +18 -0
  19. package/dist/jest-teardown.cjs.map +1 -0
  20. package/dist/jest-teardown.d.cts +13 -0
  21. package/dist/jest-teardown.d.mts +14 -0
  22. package/dist/jest-teardown.mjs +18 -0
  23. package/dist/jest-teardown.mjs.map +1 -0
  24. package/dist/jest-template.cjs +44 -0
  25. package/dist/jest-template.cjs.map +1 -0
  26. package/dist/jest-template.d.cts +31 -0
  27. package/dist/jest-template.d.mts +31 -0
  28. package/dist/jest-template.mjs +38 -0
  29. package/dist/jest-template.mjs.map +1 -0
  30. package/dist/jest.cjs +12 -15
  31. package/dist/jest.cjs.map +1 -1
  32. package/dist/jest.d.cts +10 -7
  33. package/dist/jest.d.mts +10 -6
  34. package/dist/jest.mjs +12 -10
  35. package/dist/jest.mjs.map +1 -1
  36. package/dist/lease-B3TuX92y.d.mts +24 -0
  37. package/dist/lease-DS1SX8U_.mjs +221 -0
  38. package/dist/lease-DS1SX8U_.mjs.map +1 -0
  39. package/dist/lease-WlmOnNDi.cjs +310 -0
  40. package/dist/lease-WlmOnNDi.cjs.map +1 -0
  41. package/dist/lease-t4I9JahV.d.cts +24 -0
  42. package/dist/lifecycle-BbrvFQvg.cjs +953 -0
  43. package/dist/lifecycle-BbrvFQvg.cjs.map +1 -0
  44. package/dist/lifecycle-BvIx0xqq.mjs +781 -0
  45. package/dist/lifecycle-BvIx0xqq.mjs.map +1 -0
  46. package/dist/load-dev-env-BULkXyen.mjs +10 -0
  47. package/dist/load-dev-env-BULkXyen.mjs.map +1 -0
  48. package/dist/load-dev-env-Bl6Ddv1U.cjs +15 -0
  49. package/dist/load-dev-env-Bl6Ddv1U.cjs.map +1 -0
  50. package/dist/load-mode-env-CX9vd56X.cjs +39 -0
  51. package/dist/load-mode-env-CX9vd56X.cjs.map +1 -0
  52. package/dist/load-mode-env-Ce9ujisN.mjs +28 -0
  53. package/dist/load-mode-env-Ce9ujisN.mjs.map +1 -0
  54. package/dist/load-test-env-BqtA6V-d.mjs +18 -0
  55. package/dist/load-test-env-BqtA6V-d.mjs.map +1 -0
  56. package/dist/load-test-env-Dcgh6YHV.cjs +23 -0
  57. package/dist/load-test-env-Dcgh6YHV.cjs.map +1 -0
  58. package/dist/naming-C2nGVxPk.d.cts +39 -0
  59. package/dist/naming-C2nGVxPk.d.mts +39 -0
  60. package/dist/nx.cjs +25 -18
  61. package/dist/nx.cjs.map +1 -1
  62. package/dist/nx.d.cts +9 -20
  63. package/dist/nx.d.mts +9 -20
  64. package/dist/nx.mjs +18 -18
  65. package/dist/nx.mjs.map +1 -1
  66. package/dist/status-4Uz6A3LB.cjs +31 -0
  67. package/dist/status-4Uz6A3LB.cjs.map +1 -0
  68. package/dist/status-Dcnpgnlz.mjs +26 -0
  69. package/dist/status-Dcnpgnlz.mjs.map +1 -0
  70. package/dist/studio-DIFw1qWC.mjs +173 -0
  71. package/dist/studio-DIFw1qWC.mjs.map +1 -0
  72. package/dist/studio-oJE30_PO.cjs +208 -0
  73. package/dist/studio-oJE30_PO.cjs.map +1 -0
  74. package/dist/tasks-CNHvvlsN.cjs +67 -0
  75. package/dist/tasks-CNHvvlsN.cjs.map +1 -0
  76. package/dist/tasks-CjaZRi_G.d.mts +19 -0
  77. package/dist/tasks-D5iWIdlo.mjs +38 -0
  78. package/dist/tasks-D5iWIdlo.mjs.map +1 -0
  79. package/dist/tasks-DdQoP9We.d.cts +19 -0
  80. package/dist/template-BiJ-0TIF.mjs +88 -0
  81. package/dist/template-BiJ-0TIF.mjs.map +1 -0
  82. package/dist/template-CXMpODzK.cjs +105 -0
  83. package/dist/template-CXMpODzK.cjs.map +1 -0
  84. package/dist/template-mode-BTUsdBtA.mjs +94 -0
  85. package/dist/template-mode-BTUsdBtA.mjs.map +1 -0
  86. package/dist/template-mode-CzyVwHsi.d.cts +31 -0
  87. package/dist/template-mode-CzyVwHsi.d.mts +31 -0
  88. package/dist/template-mode-JTOxrEgE.cjs +105 -0
  89. package/dist/template-mode-JTOxrEgE.cjs.map +1 -0
  90. package/dist/test-env.cjs +16 -0
  91. package/dist/test-env.cjs.map +1 -0
  92. package/dist/test-env.d.cts +1 -0
  93. package/dist/test-env.d.mts +1 -0
  94. package/dist/test-env.mjs +18 -0
  95. package/dist/test-env.mjs.map +1 -0
  96. package/dist/vite-plus.cjs +113 -35
  97. package/dist/vite-plus.cjs.map +1 -1
  98. package/dist/vite-plus.d.cts +26 -46
  99. package/dist/vite-plus.d.mts +26 -46
  100. package/dist/vite-plus.mjs +110 -32
  101. package/dist/vite-plus.mjs.map +1 -1
  102. package/dist/vitest-template.cjs +48 -0
  103. package/dist/vitest-template.cjs.map +1 -0
  104. package/dist/vitest-template.d.cts +31 -0
  105. package/dist/vitest-template.d.mts +31 -0
  106. package/dist/vitest-template.mjs +42 -0
  107. package/dist/vitest-template.mjs.map +1 -0
  108. package/dist/vitest.cjs +10 -10
  109. package/dist/vitest.cjs.map +1 -1
  110. package/dist/vitest.d.cts +7 -3
  111. package/dist/vitest.d.mts +7 -2
  112. package/dist/vitest.mjs +10 -5
  113. package/dist/vitest.mjs.map +1 -1
  114. package/package.json +46 -13
  115. package/scripts/autopg-version +1 -0
  116. package/scripts/ci-install-autopg.sh +73 -0
  117. package/scripts/postinstall.js +35 -9
  118. package/dist/constants-BY97wXjA.mjs +0 -24
  119. package/dist/constants-BY97wXjA.mjs.map +0 -1
  120. package/dist/constants-CNZn5Xro.cjs +0 -41
  121. package/dist/constants-CNZn5Xro.cjs.map +0 -1
  122. package/dist/lifecycle-BGYtVXtx.cjs +0 -743
  123. package/dist/lifecycle-BGYtVXtx.cjs.map +0 -1
  124. package/dist/lifecycle-CT_8AWPv.mjs +0 -577
  125. package/dist/lifecycle-CT_8AWPv.mjs.map +0 -1
  126. package/dist/tasks-13P6tMth.mjs +0 -16
  127. package/dist/tasks-13P6tMth.mjs.map +0 -1
  128. package/dist/tasks-B8ryV9xo.cjs +0 -21
  129. package/dist/tasks-B8ryV9xo.cjs.map +0 -1
package/README.md CHANGED
@@ -1,121 +1,102 @@
1
1
  # cedar-pg
2
2
 
3
- Worktree-isolated local Postgres for **Vite+**, **Nx**, and **CedarJS**, powered by [autopg](https://github.com/automagik-dev/autopg).
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 on npm as **`@cedarjs/pg`**.
5
+ Published as `@cedarjs/pg`. CLI: `cedarpg`. Beta (`0.2.0-beta.0`, `beta` dist-tag). Public APIs may still change.
6
6
 
7
- > **Alpha** (`0.1.0-alpha.0`): APIs may change. Install with the `alpha` dist-tag.
8
-
9
- ## What you get (via autopg)
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 |
21
-
22
- ## What cedar-pg adds
23
-
24
- **cedar-pg** gives each **git worktree** its own database and role on that autopg host (readable names, leases, teardown) so parallel checkouts do not share one DB:
25
-
26
- - **1 database per git worktree** (visible in `\l` as `cpg_…`)
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
30
-
31
- | Layer | Responsibility |
32
- | ------------ | ------------------------------------------------------------------- |
33
- | **autopg** | Embedded Postgres host (concurrent, zero-config, auto-provision) |
34
- | **cedar-pg** | Per-worktree `CREATE DATABASE` / role, `DATABASE_URL`, dispose + GC |
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 |
35
11
 
36
12
  ## Install
37
13
 
38
- ```bash
39
- npm install -D @cedarjs/pg@alpha
40
- # or: pnpm add -D @cedarjs/pg@alpha
41
- # or: yarn add -D @cedarjs/pg@alpha
42
- ```
43
-
44
- ## Database names (observability)
14
+ Node `>=24`.
45
15
 
46
- ```text
47
- cpg_<repo>_<worktree>_<mode>_<pathHash8>
16
+ ```bash
17
+ npm install -D @cedarjs/pg@beta
18
+ # pnpm add -D @cedarjs/pg@beta
19
+ # yarn add -D @cedarjs/pg@beta
48
20
  ```
49
21
 
50
- Examples:
22
+ `postinstall` installs the pinned autopg binary when it is missing. The pin is `scripts/autopg-version`. To bump autopg, change that file.
51
23
 
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 |
24
+ To install the host by hand (local, non-CI; upstream `install.sh` may use pm2):
56
25
 
57
- ## Prerequisites
26
+ ```bash
27
+ VER=$(tr -d '[:space:]' < scripts/autopg-version)
28
+ curl -fsSL "https://raw.githubusercontent.com/automagik-dev/autopg/${VER}/install.sh" \
29
+ | AUTOPG_VERSION="$VER" bash
30
+ ```
58
31
 
59
- A running [autopg](https://github.com/automagik-dev/autopg) host (installed automatically by `postinstall`, or manually).
60
- Both the install script and binary are pinned to a release tag (currently `v3.0.7`); bump the pin in `scripts/postinstall.js` to upgrade:
32
+ Once per machine, run the host (`autopg daemon`, or your usual install). Then per worktree:
61
33
 
62
34
  ```bash
63
- curl -fsSL https://raw.githubusercontent.com/automagik-dev/autopg/v3.0.7/install.sh \
64
- | AUTOPG_VERSION=v3.0.7 bash
35
+ cedarpg acquire --mode=dev
65
36
  ```
66
37
 
67
- Typical flow: `autopg daemon` (or your usual host install) once per machine → `cedarpg ensure` per worktree → connect with the printed `DATABASE_URL`.
38
+ Connect with the printed `DATABASE_URL`.
68
39
 
69
- ## Develop this package (Vite+)
40
+ From another checkout of this repo, pack first, then depend on the build:
70
41
 
71
42
  ```bash
72
- vp install
73
- vp check
74
- vp test
75
- vp pack # → dist/ (dts + esm + cjs)
76
- vp run smoke # build → npm-pack tarball → install + resolve exports
43
+ vp pack
44
+ # in the app: yarn add @cedarjs/pg@file:../cedar-pg
45
+ # or install the tarball vp pack writes (name includes the version in package.json)
77
46
  ```
78
47
 
79
- ## Local consume (without npm)
48
+ ## Acquire a database
80
49
 
81
- ```bash
82
- # in this repo
83
- vp pack
50
+ `dev` databases persist across restarts. `test` databases drop on `dispose`.
51
+
52
+ Names look like this (visible in `psql` `\l`):
84
53
 
85
- # in your app / Cedar
86
- yarn add @cedarjs/pg@file:../cedar-pg
87
- # or: pnpm pack && yarn add ./cedarjs-pg-0.1.0-alpha.0.tgz
54
+ ```text
55
+ cpg_<repo>_<worktree>_<mode>_<pathHash8>
88
56
  ```
89
57
 
58
+ Worktree state lives in `.cedarpg`. Import `STATE_DIRNAME` instead of hardcoding that string. `gc` uses `~/.cedarpg/registry`.
59
+
90
60
  ## CLI
91
61
 
92
62
  ```bash
93
- cedarpg ensure --mode=dev
94
- cedarpg ensure --mode=test --print-env
63
+ cedarpg acquire --mode=dev
64
+ cedarpg acquire --mode=test --print-env
65
+ cedarpg run --mode=dev -- yarn tsx scripts/apiServer/dev.ts
66
+ cedarpg run --mode=test -- vitest run
95
67
  cedarpg dispose --mode=test
96
68
  cedarpg print-url --mode=dev
97
- cedarpg gc # drop DBs whose worktree root is gone (uses ~/.cedarpg/registry)
69
+ cedarpg status --mode=dev
70
+ cedarpg studio --mode=dev # Prisma or Drizzle Studio (--prisma / --drizzle)
71
+ cedarpg gc # drop DBs whose worktree root is gone
98
72
  ```
99
73
 
100
- ## Vite+ consumer adapter
74
+ `status` and `studio` are read-only. They do not acquire. `studio` walks from cwd up to the worktree.
75
+
76
+ `cedarpg run` acquires or attaches the lease, 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.
77
+
78
+ If two targets run `cedarpg acquire` or `cedarpg run` on the same worktree, role and database DDL can race. Use one acquire, then `run` wrappers.
79
+
80
+ ## Vite+
101
81
 
102
82
  ```ts
103
83
  // vite.config.ts
104
84
  import { defineConfig } from "vite-plus";
105
- import { cedarPgTasks } from "@cedarjs/pg/vite-plus";
85
+ import { cedarPgTasks, cedarPgDev } from "@cedarjs/pg/vite-plus";
106
86
 
107
87
  export default defineConfig({
88
+ plugins: [cedarPgDev()],
108
89
  run: {
109
90
  tasks: {
110
91
  ...cedarPgTasks(),
111
92
  test: {
112
93
  command: "vp test",
113
- dependsOn: ["db:ensure-test"],
94
+ dependsOn: ["db:acquire-test"],
114
95
  env: ["DATABASE_URL", "TEST_DATABASE_URL"],
115
96
  },
116
97
  dev: {
117
98
  command: "vp dev",
118
- dependsOn: ["db:ensure"],
99
+ dependsOn: ["db:acquire"],
119
100
  env: ["DATABASE_URL"],
120
101
  },
121
102
  },
@@ -123,31 +104,322 @@ export default defineConfig({
123
104
  });
124
105
  ```
125
106
 
107
+ `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`):
108
+
109
+ | Key | Action |
110
+ | --- | ---------------------------------------------------------------------- |
111
+ | `d` | Reprint status (name, port, `DATABASE_URL`, env file) |
112
+ | `s` | Open Prisma Studio or Drizzle Kit Studio with the lease `DATABASE_URL` |
113
+
114
+ 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.
115
+
116
+ 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.
117
+
118
+ ## Nx
119
+
120
+ Nx `dependsOn` does not forward env from an acquire task into dependents. Vite+ `env: [...]` does. Canonical shape:
121
+
122
+ 1. One `db:ready` (or `createAcquireTask`) that acquires and migrates.
123
+ 2. Wrap API, dev, and e2e children with `cedarpg run --mode=dev --force -- <cmd>`.
124
+
125
+ Pointing Nx `envFile` at `.cedarpg/<mode>.env` after acquire still loses to an ambient `.env` unless you also force or overwrite.
126
+
127
+ ```ts
128
+ import { cedarPgNxTargets, cedarPgRunCommand, relativeEnvFile } from "@cedarjs/pg/nx";
129
+
130
+ cedarPgNxTargets();
131
+ // { "db:acquire": { command: "cedarpg acquire --mode=dev", cache: false }, ... }
132
+
133
+ cedarPgRunCommand("dev", "yarn tsx scripts/apiServer/dev.ts");
134
+ // "cedarpg run --mode=dev -- yarn tsx scripts/apiServer/dev.ts"
135
+
136
+ relativeEnvFile("dev"); // ".cedarpg/dev.env"
137
+ ```
138
+
139
+ ```json
140
+ {
141
+ "targets": {
142
+ "db:ready": { "command": "tsx tools/db-ready.ts", "cache": false },
143
+ "dev": {
144
+ "dependsOn": ["db:ready"],
145
+ "command": "cedarpg run --mode=dev --force -- yarn tsx scripts/apiServer/dev.ts"
146
+ },
147
+ "serve": {
148
+ "dependsOn": ["db:ready"],
149
+ "command": "cedarpg run --mode=dev --force -- node dist/server.js"
150
+ }
151
+ }
152
+ }
153
+ ```
154
+
155
+ Migrate hook (same compose shape as Jest `createGlobalSetup`):
156
+
157
+ ```ts
158
+ // tools/db-ready.ts
159
+ import { createAcquireTask } from "@cedarjs/pg";
160
+
161
+ await createAcquireTask({
162
+ mode: "dev",
163
+ // Need this when .env already has DATABASE_URL
164
+ force: true,
165
+ afterAcquire: async ({ databaseUrl }) => {
166
+ // prisma migrate deploy, drizzle push, ...
167
+ },
168
+ })();
169
+ ```
170
+
171
+ If you cannot wrap with `run`, use `loadDevEnv({ overwrite: true })` or `import "@cedarjs/pg/dev-env"`. Absolute path helper: `envFilePath(root, mode)`.
172
+
173
+ ## Vitest and Jest
174
+
175
+ 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).
176
+
177
+ ```ts
178
+ // vitest.config.ts
179
+ import { defineConfig } from "vitest/config";
180
+
181
+ export default defineConfig({
182
+ test: {
183
+ globalSetup: ["@cedarjs/pg/vitest"],
184
+ },
185
+ });
186
+ ```
187
+
188
+ 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.
189
+
190
+ ```js
191
+ // jest.config.cjs
192
+ module.exports = {
193
+ globalSetup: require.resolve("@cedarjs/pg/jest"),
194
+ globalTeardown: require.resolve("@cedarjs/pg/jest-teardown"),
195
+ // Jest globalSetup is a separate process. Workers load DATABASE_URL from .cedarpg/test.env.
196
+ setupFiles: [require.resolve("@cedarjs/pg/test-env")],
197
+ };
198
+ ```
199
+
200
+ `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).
201
+
202
+ ## CedarJS and custom globalSetup
203
+
204
+ If the runner already owns `globalSetup` (Prisma push or migrate after acquire), do not replace it with `@cedarjs/pg/jest`. Compose:
205
+
206
+ 1. In `globalSetup`, call `acquireIfNeeded` when opted in, then migrate.
207
+ 2. Add `setupFiles: [require.resolve("@cedarjs/pg/test-env")]` so Jest workers see `DATABASE_URL`.
208
+ 3. In `globalTeardown`, call `dispose({ mode: "test", root })`.
209
+
210
+ ```ts
211
+ import { acquireIfNeeded } from "@cedarjs/pg";
212
+
213
+ if (process.env.CEDAR_PG === "1" || process.env.CEDAR_PG === "true") {
214
+ await acquireIfNeeded({
215
+ root: projectRoot, // e.g. getPaths().base
216
+ mode: "test",
217
+ setEnv: true, // this process (prisma). Workers use @cedarjs/pg/test-env.
218
+ url: process.env.TEST_DATABASE_URL,
219
+ force: process.env.CEDAR_PG_FORCE === "1",
220
+ disabled: false, // framework opt-in. Stock adapters use CEDAR_PG=0 opt-out.
221
+ });
222
+ }
223
+ ```
224
+
225
+ ## TEMPLATE clones
226
+
227
+ Migrate stays app-owned via `createGlobalSetup({ migrate })`. The adapter then marks TEMPLATE and clones per worker.
228
+
229
+ Point `globalSetup` at a local module that calls `createGlobalSetup`. String-resolving the package entry without a migrate hook throws.
230
+
231
+ ### Jest
232
+
233
+ ```js
234
+ // jest.cedar-global.cjs
235
+ const { createGlobalSetup } = require("@cedarjs/pg/jest/template");
236
+ module.exports = createGlobalSetup({
237
+ migrate: async ({ databaseUrl }) => {
238
+ // prisma migrate reset, drizzle push
239
+ },
240
+ });
241
+
242
+ // jest.config.cjs
243
+ // When .env has a real TEST_DATABASE_URL, set FORCE once here so it
244
+ // inherits into globalSetup and workers (dotenv will not override existing keys).
245
+ process.env.CEDAR_PG_FORCE = "1";
246
+
247
+ module.exports = {
248
+ globalSetup: "<rootDir>/jest.cedar-global.cjs",
249
+ globalTeardown: require.resolve("@cedarjs/pg/jest-teardown"),
250
+ // Prefer setupFilesAfterEnv so you can use beforeAll (Jest globals).
251
+ setupFilesAfterEnv: ["<rootDir>/jest.cedar-worker.cjs"],
252
+ };
253
+
254
+ // jest.cedar-worker.cjs
255
+ const { cloneWorkerDatabase } = require("@cedarjs/pg/jest/template");
256
+ beforeAll(() => cloneWorkerDatabase());
257
+ ```
258
+
259
+ `cloneWorkerDatabase` memos on `globalThis`, so Jest `setupFiles` (module reload per file) still shares one clone per worker.
260
+
261
+ ### Vitest
262
+
263
+ ```ts
264
+ // vitest.cedar-global.ts
265
+ import { createGlobalSetup } from "@cedarjs/pg/vitest/template";
266
+ export default createGlobalSetup({
267
+ migrate: async ({ databaseUrl }) => {
268
+ // migrate once
269
+ },
270
+ });
271
+
272
+ // vitest.config.ts
273
+ export default defineConfig({
274
+ test: {
275
+ globalSetup: ["./vitest.cedar-global.ts"],
276
+ setupFiles: ["./vitest.cedar-worker.ts"],
277
+ },
278
+ });
279
+
280
+ // vitest.cedar-worker.ts
281
+ import { cloneWorkerDatabase } from "@cedarjs/pg/vitest/template";
282
+ await cloneWorkerDatabase();
283
+ ```
284
+
285
+ ### Core API
286
+
287
+ No runner adapters.
288
+
289
+ ```ts
290
+ import { acquire, markTemplate, cloneFromTemplate, dispose } from "@cedarjs/pg";
291
+
292
+ const acquired = await acquire({ mode: "test" });
293
+ await migrate({ databaseUrl: acquired.databaseUrl, adminUrl: acquired.adminUrl });
294
+ await markTemplate({ root: acquired.root, mode: "test", adminUrl: acquired.adminUrl });
295
+ const worker = await cloneFromTemplate({
296
+ root: acquired.root,
297
+ mode: "test",
298
+ name: "1",
299
+ setEnv: true,
300
+ });
301
+ await worker.dropClone(); // optional: drop one clone only
302
+ await dispose({ root: acquired.root, mode: "test" }); // TEMPLATE + all clones + role
303
+ ```
304
+
305
+ `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.
306
+
307
+ `setEnv` defaults to false on `cloneFromTemplate`. It defaults to true on `cloneFromTemplateIfNeeded` (same as `acquireIfNeeded`). Worker adapters call `cloneFromTemplateIfNeeded` via `cloneWorkerDatabase`.
308
+
309
+ `dispose` is role-scoped suite teardown, not `dropClone`. It unsets `IS_TEMPLATE` and drops every database owned by the lease role.
310
+
311
+ ## Host and CI
312
+
313
+ `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.
314
+
315
+ When nothing is listening, cedar-pg brings up the registered local host. It runs `autopg restart`, then `autopg install` if the host was never registered. It attaches once TCP accepts. Same port, same `~/.autopg/data`. It does not start a second local Postgres. If neither verb produces a listener, `acquire` fails with what it tried.
316
+
317
+ Callers use `acquire` (and `adminUrl`). There is no public host-options object. Ephemeral behavior is env-driven.
318
+
319
+ ```ts
320
+ import { acquire } from "@cedarjs/pg";
321
+
322
+ // CI=true: detached postmaster (--ram on Linux /dev/shm). Does not rewrite ~/.autopg.
323
+ const { databaseUrl } = await acquire({ mode: "test" });
324
+ ```
325
+
326
+ | Signal | Effect (attach always wins when something is listening) |
327
+ | --------------------------- | ----------------------------------------------------------------------------- |
328
+ | `CEDAR_PG_EPHEMERAL_HOST=1` | Ephemeral owned postmaster on 55432 |
329
+ | `CEDAR_PG_EPHEMERAL_HOST=0` | Never own a postmaster (even when `CI=true`). Local autopg host only, or fail |
330
+ | unset and `CI=true` | Ephemeral |
331
+ | unset | Local: `autopg restart`, then `autopg install`, then fail |
332
+
333
+ Ephemeral recipe (not configurable via cedar-pg):
334
+
335
+ - Detached `autopg postmaster --port 55432 --socket-dir DIR --data DIR`
336
+ - Does not run `autopg install` (that rewrites `~/.autopg/admin.json` and conflicts with a local pm2 host)
337
+ - Linux when `/dev/shm` exists: also `--ram` and `DIR=/dev/shm/cedar-pg-<uid>`
338
+ - Otherwise: disk `DIR` under the OS temp dir
339
+ - Ready when TCP accepts on the recipe port
340
+ - Before cold-start, if the recipe port is not live, cedar-pg prunes leftover `/dev/shm/cedar-pg-*`, `pgserve-*`, and `PostgreSQL.*` (OOM-killed runs filling tmpfs). Safe on isolated CI VMs. On shared self-hosted runners another job's leftovers could match those globs.
341
+
342
+ 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.
343
+
344
+ 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).
345
+
346
+ ### GitHub Actions
347
+
348
+ 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).
349
+
350
+ ```yaml
351
+ - uses: actions/checkout@v6
352
+ # In cedar-pg:
353
+ - uses: ./.github/actions/setup-autopg
354
+ # From another repo (pin to a tag when publishing the action):
355
+ # - uses: cedarjs/cedar-pg/.github/actions/setup-autopg@main
356
+ ```
357
+
358
+ 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.
359
+
360
+ 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.
361
+
362
+ ```yaml
363
+ - name: Ensure autopg binary
364
+ run: |
365
+ set -euo pipefail
366
+ echo "${HOME}/.local/bin" >> "${GITHUB_PATH}"
367
+ export PATH="${HOME}/.local/bin:${PATH}"
368
+ bash node_modules/@cedarjs/pg/scripts/ci-install-autopg.sh
369
+ env:
370
+ GH_TOKEN: ${{ github.token }}
371
+ ```
372
+
373
+ ## Environment variables
374
+
375
+ | Var | Meaning |
376
+ | -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
377
+ | `AUTOPG_BIN` | Path to the autopg binary |
378
+ | `AUTOPG_PG_USER`, `AUTOPG_PG_PASSWORD` | Autopg superuser for admin URL (default user `postgres`, password `postgres`) |
379
+ | `CEDAR_PG=0` | Disable auto-acquire in adapters |
380
+ | `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) |
381
+ | `CEDAR_PG_FORCE=1` | Ignore the external-URL escape hatch (adapters, `cedarpg acquire --force`, `cedarpg run --force`) |
382
+ | `CEDAR_PG_EPHEMERAL_HOST` | `1` owned postmaster. `0` never own one (even in CI). Unset and `CI=true` means ephemeral |
383
+ | `CEDAR_PG_REGISTRY_DIR` | Override the global lease registry (for `gc`) |
384
+ | `CEDAR_PG_SKIP_POSTINSTALL=1` | Skip the autopg install hook |
385
+ | `CEDAR_PG_INSTALL_AUTOPG=1` | Under `CI=true`, run binary-only `ci-install-autopg.sh` from postinstall |
386
+
387
+ `force`, `overwrite`, and `run` are different knobs. Do not collapse them.
388
+
126
389
  ## Programmatic API
127
390
 
128
391
  ```ts
129
- import { ensure, dispose } from "@cedarjs/pg";
392
+ import { acquire, loadDevEnv } from "@cedarjs/pg";
393
+
394
+ const { databaseUrl, adminUrl, databaseName, dispose } = await acquire({ mode: "test" });
395
+ await dispose();
130
396
 
131
- const { databaseUrl, databaseName, dispose: drop } = await ensure({ mode: "test" });
132
- // … tests …
133
- await drop();
397
+ loadDevEnv({ overwrite: true }); // override .env DATABASE_URL from .cedarpg/dev.env
134
398
  ```
135
399
 
136
- ## Env
400
+ 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).
137
401
 
138
- | Var | Meaning |
139
- | ----------------------------- | ------------------------------------------------------------------ |
140
- | `AUTOPG_BIN` | Path to autopg |
141
- | `CEDAR_PG=0` | Disable auto-ensure in adapters |
142
- | `TEST_DATABASE_URL` | Escape hatch: skip ensure for external DBs (not `cpg_*` / `file:`) |
143
- | `CEDAR_PG_FORCE=1` | Ignore external-URL escape hatch |
144
- | `CEDAR_PG_REGISTRY_DIR` | Override global lease registry (for `gc`) |
145
- | `CEDAR_PG_SKIP_POSTINSTALL=1` | Skip autopg install hook |
146
- | `CEDAR_PG_INSTALL_AUTOPG=1` | Force autopg install in CI |
402
+ ## Develop this package
147
403
 
148
- ## Alpha caveats
404
+ Vite+, Node `>=24` (`.node-version`), `pnpm@11`. Contributor rules: [AGENTS.md](AGENTS.md). User-visible API changes: [CHANGELOG.md](CHANGELOG.md).
405
+
406
+ ```bash
407
+ vp install
408
+ vp check
409
+ vp test
410
+ vp pack # dist/ (dts + esm + cjs)
411
+ vp run smoke # pack, then tarball install, then resolve exports
412
+ vp run smoke:pg # pack, then Vitest and Jest adapters against real ephemeral Postgres
413
+ ```
149
414
 
150
- - Public API may change before `0.1.0`.
151
- - End-to-end Postgres flows assume a working local `autopg` host; CI unit tests do not start Postgres.
152
- - State lives in product-owned `.cedarpg` (worktree + `~/.cedarpg/registry`), not under autopg's `~/.autopg/` or a generic `.pg`.
153
- - Password salt (`cedar-pg\\0`, scheme v1) is an opaque crypto constant; bump the scheme id to change it.
415
+ ## Troubleshooting
416
+
417
+ | Symptom | Fix |
418
+ | --------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
419
+ | `ECONNREFUSED 127.0.0.1:25432` on `cedarpg acquire` | autopg is registered but not listening. cedar-pg runs `autopg restart` (then `install`) and attaches once TCP accepts. `restart` exiting 0 is not proof. When pm2 is missing it still prints "respawned daemon". If both verbs fail, install pm2 or fix the supervisor (`pm2 logs autopg-server`). |
420
+ | `database already exists: ..._c_<workerId>` in Jest | Use current `@cedarjs/pg` (`cloneWorkerDatabase` memos on `globalThis`). Prefer `setupFilesAfterEnv` + `beforeAll`. Avoid passing bare `JEST_WORKER_ID` as an explicit `name`. |
421
+ | 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`. |
422
+ | `Disk quota exceeded` / `No space left on device` / Postgres `53100` on ephemeral start | Enlarge `/dev/shm` (`sudo mount -o remount,size=6G /dev/shm`). On isolated runners only: `rm -rf /dev/shm/cedar-pg-* /dev/shm/pgserve-* /dev/shm/PostgreSQL.*`. Cold-start also prunes these when the recipe port is dead. |
423
+ | `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. |
424
+ | Nx child still uses `.env` `DATABASE_URL` | `dependsOn` does not forward acquire env. Wrap with `cedarpg run --mode=dev --force -- <cmd>`, or `loadDevEnv({ overwrite: true })`. |
425
+ | Role or DB errors under parallel Nx targets | Do not run concurrent `acquire` or `run` on the same worktree. One `db:ready`, then `run` wrappers. |