@rebasepro/cli 0.24.0 → 0.24.1-canary.g19596ef

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 (156) hide show
  1. package/bin/rebase.js +3 -0
  2. package/dist/agent-setup-B31F00DC.js +324 -0
  3. package/dist/agent-setup-B31F00DC.js.map +1 -0
  4. package/dist/api-keys-Bl1I9q7M.js +525 -0
  5. package/dist/api-keys-Bl1I9q7M.js.map +1 -0
  6. package/dist/apps-CNgQJjg5.js +340 -0
  7. package/dist/apps-CNgQJjg5.js.map +1 -0
  8. package/dist/args-BnmsEXvu.js +109 -0
  9. package/dist/args-BnmsEXvu.js.map +1 -0
  10. package/dist/atlas-dev-database-D_LP_Woq.js +62 -0
  11. package/dist/atlas-dev-database-D_LP_Woq.js.map +1 -0
  12. package/dist/auth-aySHEF45.js +414 -0
  13. package/dist/auth-aySHEF45.js.map +1 -0
  14. package/dist/branch-pointer-DvSHBjjz.js +2 -0
  15. package/dist/build-DIHWIqO4.js +336 -0
  16. package/dist/build-DIHWIqO4.js.map +1 -0
  17. package/dist/bundle-CqURVN-K.js +1621 -0
  18. package/dist/bundle-CqURVN-K.js.map +1 -0
  19. package/dist/bundle.d.ts +6 -1
  20. package/dist/cli.d.ts +2 -2
  21. package/dist/cloud-DjByQlkW.js +9667 -0
  22. package/dist/cloud-DjByQlkW.js.map +1 -0
  23. package/dist/collection-drift-BKj_BoMq.js +106 -0
  24. package/dist/collection-drift-BKj_BoMq.js.map +1 -0
  25. package/dist/commands/api-keys.d.ts +2 -0
  26. package/dist/commands/console.d.ts +12 -0
  27. package/dist/commands/db.d.ts +64 -24
  28. package/dist/commands/dev.d.ts +103 -6
  29. package/dist/commands/generate_sdk.d.ts +26 -0
  30. package/dist/commands/init.d.ts +58 -0
  31. package/dist/commands/skills.d.ts +22 -7
  32. package/dist/commands/start.d.ts +15 -0
  33. package/dist/commands/storage.d.ts +46 -0
  34. package/dist/commands/upgrade.d.ts +1 -0
  35. package/dist/consent-ClRHmcxf.js +76 -0
  36. package/dist/consent-ClRHmcxf.js.map +1 -0
  37. package/dist/console-D6SJf8k_.js +102 -0
  38. package/dist/console-D6SJf8k_.js.map +1 -0
  39. package/dist/context-BqhRydBg.js +1234 -0
  40. package/dist/context-BqhRydBg.js.map +1 -0
  41. package/dist/{daemon-entry-LTFKtpHy.js → daemon-entry-Da-D3rmH.js} +3 -4
  42. package/dist/daemon-entry-Da-D3rmH.js.map +1 -0
  43. package/dist/daemon-pzsQwDHa.js +2 -0
  44. package/dist/db-B7RuNuaQ.js +1312 -0
  45. package/dist/db-B7RuNuaQ.js.map +1 -0
  46. package/dist/derive-Dl0z2OWi.js +484 -0
  47. package/dist/derive-Dl0z2OWi.js.map +1 -0
  48. package/dist/dev-CYr_WXan.js +1599 -0
  49. package/dist/dev-CYr_WXan.js.map +1 -0
  50. package/dist/dev-db/atlas-dev-database.d.ts +32 -0
  51. package/dist/dev-db/daemon-entry.d.ts +13 -0
  52. package/dist/dev-db/last-dev-database.d.ts +41 -0
  53. package/dist/dev-db/prepare.d.ts +19 -3
  54. package/dist/dev-db/pull.d.ts +90 -5
  55. package/dist/dev-db/resolve.d.ts +26 -2
  56. package/dist/dev-preflight-B-LYyBf1.js +2 -0
  57. package/dist/dev-server-DG8Tt-uf.js +96 -0
  58. package/dist/dev-server-DG8Tt-uf.js.map +1 -0
  59. package/dist/doctor-BP8cZ-7F.js +771 -0
  60. package/dist/doctor-BP8cZ-7F.js.map +1 -0
  61. package/dist/eject-DizdoecV.js +335 -0
  62. package/dist/eject-DizdoecV.js.map +1 -0
  63. package/dist/fold-static-BC9CYicw.js +201 -0
  64. package/dist/fold-static-BC9CYicw.js.map +1 -0
  65. package/dist/function-portability-BIvlvR9q.js +265 -0
  66. package/dist/function-portability-BIvlvR9q.js.map +1 -0
  67. package/dist/generate_sdk-BPqdJc3Q.js +489 -0
  68. package/dist/generate_sdk-BPqdJc3Q.js.map +1 -0
  69. package/dist/index.es.js +151 -24674
  70. package/dist/index.es.js.map +1 -1
  71. package/dist/init-Utstvh2Q.js +1149 -0
  72. package/dist/init-Utstvh2Q.js.map +1 -0
  73. package/dist/last-dev-database-B58g89uA.js +2 -0
  74. package/dist/manifest-DRp0UVTj.js +880 -0
  75. package/dist/manifest-DRp0UVTj.js.map +1 -0
  76. package/dist/manifest.d.ts +15 -3
  77. package/dist/normalize-imports-X0Tgze9y.js +107 -0
  78. package/dist/normalize-imports-X0Tgze9y.js.map +1 -0
  79. package/dist/{package-manager-CXQevsE1.js → package-manager-DSEOwTqM.js} +2 -2
  80. package/dist/{package-manager-CXQevsE1.js.map → package-manager-DSEOwTqM.js.map} +1 -1
  81. package/dist/path-flags-BFgG9V6y.js +91 -0
  82. package/dist/path-flags-BFgG9V6y.js.map +1 -0
  83. package/dist/precompress.d.ts +17 -0
  84. package/dist/prepare-CDN6lSpz.js +1236 -0
  85. package/dist/prepare-CDN6lSpz.js.map +1 -0
  86. package/dist/{project-DmaGLmH2.js → project-Wp2zoyeN.js} +33 -4
  87. package/dist/project-Wp2zoyeN.js.map +1 -0
  88. package/dist/pull-CrGYMEsy.js +458 -0
  89. package/dist/pull-CrGYMEsy.js.map +1 -0
  90. package/dist/resolve-B2O-wKKk.js +2 -0
  91. package/dist/resources-FD7AfZx8.js +183 -0
  92. package/dist/resources-FD7AfZx8.js.map +1 -0
  93. package/dist/schema-DMdr431S.js +125 -0
  94. package/dist/schema-DMdr431S.js.map +1 -0
  95. package/dist/skills-DTFibT1m.js +642 -0
  96. package/dist/skills-DTFibT1m.js.map +1 -0
  97. package/dist/start-DEa-hOBh.js +267 -0
  98. package/dist/start-DEa-hOBh.js.map +1 -0
  99. package/dist/{state-C59Elrnt.js → state-Do7ZJYkF.js} +49 -4
  100. package/dist/state-Do7ZJYkF.js.map +1 -0
  101. package/dist/status-DxnJTekv.js +352 -0
  102. package/dist/status-DxnJTekv.js.map +1 -0
  103. package/dist/storage-DyVq-f_F.js +303 -0
  104. package/dist/storage-DyVq-f_F.js.map +1 -0
  105. package/dist/telemetry/identity.d.ts +10 -1
  106. package/dist/telemetry-C8Uq8phb.js +129 -0
  107. package/dist/telemetry-C8Uq8phb.js.map +1 -0
  108. package/dist/telemetry-EMFRVRQi.js +351 -0
  109. package/dist/telemetry-EMFRVRQi.js.map +1 -0
  110. package/dist/unknown-command-DNwDcX3w.js +98 -0
  111. package/dist/unknown-command-DNwDcX3w.js.map +1 -0
  112. package/dist/upgrade-CtZtRkTE.js +1332 -0
  113. package/dist/upgrade-CtZtRkTE.js.map +1 -0
  114. package/dist/upgrade.d.ts +24 -3
  115. package/dist/utils/dev-preflight.d.ts +47 -5
  116. package/dist/utils/dev-server.d.ts +30 -0
  117. package/dist/utils/path-flags.d.ts +15 -2
  118. package/dist/utils/project.d.ts +16 -0
  119. package/dist/utils/temp-archive.d.ts +9 -0
  120. package/dist/{version-DsgBmP22.js → version-Buyyo1ut.js} +2 -2
  121. package/dist/{version-DsgBmP22.js.map → version-Buyyo1ut.js.map} +1 -1
  122. package/package.json +7 -7
  123. package/templates/eject/Dockerfile +4 -2
  124. package/templates/eject/backend/src/env.ts +1 -1
  125. package/templates/eject/backend/src/index.ts +29 -4
  126. package/templates/eject/docker-compose.custom.yml +8 -4
  127. package/templates/generated-schemas/blank.ts +40 -0
  128. package/templates/generated-schemas/blog.ts +127 -0
  129. package/templates/generated-schemas/ecommerce.ts +93 -0
  130. package/templates/overlays/baas/config/package.json +1 -0
  131. package/templates/overlays/baas/package.json +1 -0
  132. package/templates/template/.env.example +10 -4
  133. package/templates/template/README.md +11 -4
  134. package/templates/template/ai-instructions.md +3 -2
  135. package/templates/template/config/resources.ts +7 -6
  136. package/templates/template/docker-compose.yml +9 -3
  137. package/templates/template/frontend/vite.config.ts +13 -3
  138. package/templates/template/package.json +1 -0
  139. package/templates/template/scripts/example.ts +2 -1
  140. package/templates/template/tsconfig.json +25 -0
  141. package/dist/branch-pointer-kk9F5FBO.js +0 -111
  142. package/dist/branch-pointer-kk9F5FBO.js.map +0 -1
  143. package/dist/constraints-DKGbNfUW.js +0 -48
  144. package/dist/constraints-DKGbNfUW.js.map +0 -1
  145. package/dist/daemon-C8p32i-n.js +0 -357
  146. package/dist/daemon-C8p32i-n.js.map +0 -1
  147. package/dist/daemon-entry-LTFKtpHy.js.map +0 -1
  148. package/dist/dev-preflight-CNLl4rdo.js +0 -367
  149. package/dist/dev-preflight-CNLl4rdo.js.map +0 -1
  150. package/dist/project-DmaGLmH2.js.map +0 -1
  151. package/dist/pull-Dywm5FWP.js +0 -232
  152. package/dist/pull-Dywm5FWP.js.map +0 -1
  153. package/dist/resolve-BAeRUBhg.js +0 -74
  154. package/dist/resolve-BAeRUBhg.js.map +0 -1
  155. package/dist/state-C59Elrnt.js.map +0 -1
  156. package/templates/template/backend/src/schema.generated.ts +0 -13
@@ -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 { readActiveBranch as i, branch_pointer_exports as n, databaseNameOf as r, branchUrl as t };
110
-
111
- //# sourceMappingURL=branch-pointer-kk9F5FBO.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"branch-pointer-kk9F5FBO.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"}
@@ -1,357 +0,0 @@
1
- import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { a as devDbDir, c as readState, i as dataDir, l as releaseStartLock, o as findFreePort, r as clearState, s as pidRunning, t as acquireStartLock, u as stateFile } from "./state-C59Elrnt.js";
3
- import path from "path";
4
- import fs from "fs";
5
- import net from "net";
6
- import { fileURLToPath } from "url";
7
- import { randomBytes } from "crypto";
8
- import { spawn } from "child_process";
9
- //#region src/dev-db/daemon.ts
10
- /**
11
- * Starting, finding and stopping the managed database, from the caller's side.
12
- *
13
- * Every command that needs Postgres calls {@link ensureManagedDatabase} and
14
- * gets a connection string back. Whether that started a process or found one
15
- * already running is not the caller's business, which is the point: `rebase
16
- * db push`, `rebase dev` and `rebase studio` in three terminals must all reach
17
- * the same database without coordinating, because two processes opening one
18
- * PGlite data directory would corrupt it.
19
- *
20
- * The hard part is not starting the daemon; it is deciding whether the one the
21
- * state file describes is still there. A pid can be recycled after a reboot and
22
- * a port can be taken by a stranger, so believing either on its own would let
23
- * Rebase send a migration somewhere unintended. {@link isDaemonAlive} asks the
24
- * daemon to identify itself instead.
25
- */
26
- var daemon_exports = /* @__PURE__ */ __exportAll({
27
- ensureManagedDatabase: () => ensureManagedDatabase,
28
- findRunningDaemon: () => findRunningDaemon,
29
- isDaemonAlive: () => isDaemonAlive,
30
- managedUrl: () => managedUrl,
31
- pgliteLogTail: () => pgliteLogTail,
32
- resetManagedDatabase: () => resetManagedDatabase,
33
- resolveCliEntry: () => resolveCliEntry,
34
- resolveSpawn: () => resolveSpawn,
35
- stopManagedDatabase: () => stopManagedDatabase
36
- });
37
- /** How long to wait for a first boot. PGlite runs initdb on an empty data dir. */
38
- var START_TIMEOUT_MS = 6e4;
39
- /** Poll interval while waiting for the daemon to publish its state file. */
40
- var POLL_INTERVAL_MS = 150;
41
- /** How long the identity handshake may take before we call it dead. */
42
- var IDENTITY_TIMEOUT_MS = 1500;
43
- /**
44
- * PGlite's built-in superuser and database.
45
- *
46
- * Fixed by PGlite rather than chosen here, and asserted by the socket spike:
47
- * `session_user` comes back as `postgres`.
48
- */
49
- var MANAGED_USER = "postgres";
50
- var MANAGED_DATABASE = "postgres";
51
- /**
52
- * The DSN for the managed development database.
53
- *
54
- * **`sslmode=disable` is not decoration.** PGlite's socket server speaks no TLS,
55
- * and libpq-family clients try SSL first — so Atlas, which `rebase db push`
56
- * shells out to, failed the whole push with
57
- * `pq: SSL is not enabled on the server`. Its remedy box then told the reader to
58
- * append `sslmode=disable` to `DATABASE_URL`, which on this path is not set at
59
- * all: `rebase init` leaves it commented out precisely so the managed database
60
- * is used. The advice was correct and unfollowable.
61
- *
62
- * Putting it in the URL fixes every consumer at once — Atlas, `pg`, and anything
63
- * a driver shells out to later — rather than teaching each one about PGlite.
64
- */
65
- /**
66
- * The end of `pglite.log`, for an error message that has to carry its own reason.
67
- *
68
- * The daemon writes its output to a file because it is detached — it has to
69
- * outlive the command that started it. That is right, and it means every
70
- * startup failure used to be reported as a path: "see pglite.log for the
71
- * reason". On a laptop that is a fair trade. In CI it is not a trade at all,
72
- * because the runner discards the workspace: the log named in the message no
73
- * longer exists by the time anybody reads the message, so a failed run says
74
- * only that something failed.
75
- *
76
- * So the tail comes with the error. Bounded, because a log that has been
77
- * appended to across many runs is not something to paste in full.
78
- */
79
- function pgliteLogTail(projectRoot, lines = 12) {
80
- try {
81
- const contents = fs.readFileSync(path.join(devDbDir(projectRoot), "pglite.log"), "utf8").trimEnd();
82
- if (!contents) return " (pglite.log is empty — the daemon wrote nothing before exiting)";
83
- return contents.split("\n").slice(-lines).map((line) => ` ${line}`).join("\n");
84
- } catch {
85
- return " (pglite.log could not be read)";
86
- }
87
- }
88
- function managedUrl(port) {
89
- return `postgresql://${MANAGED_USER}@127.0.0.1:${port}/${MANAGED_DATABASE}?sslmode=disable`;
90
- }
91
- /**
92
- * Ask the process behind a state record to prove it is the one we wrote down.
93
- *
94
- * A pid check alone answers "is *a* process running", and a port check alone
95
- * answers "is *something* listening" — after a reboot both say yes about
96
- * strangers. The daemon answers with the token from its own state file, so a
97
- * match is the only evidence accepted.
98
- */
99
- function isDaemonAlive(state) {
100
- if (!pidRunning(state.pid)) return Promise.resolve(false);
101
- return new Promise((resolve) => {
102
- const socket = new net.Socket();
103
- let answer = "";
104
- const settle = (alive) => {
105
- socket.removeAllListeners();
106
- socket.destroy();
107
- resolve(alive);
108
- };
109
- socket.setTimeout(IDENTITY_TIMEOUT_MS);
110
- socket.once("timeout", () => settle(false));
111
- socket.once("error", () => settle(false));
112
- socket.on("data", (chunk) => {
113
- answer += chunk.toString("utf8");
114
- if (answer.includes("\n")) settle(answer.trim() === `rebase-dev-db ${state.token}`);
115
- });
116
- socket.once("close", () => settle(answer.trim() === `rebase-dev-db ${state.token}`));
117
- socket.connect(state.identityPort, "127.0.0.1");
118
- });
119
- }
120
- /** The running daemon for this project, or `null`. Never starts anything. */
121
- async function findRunningDaemon(projectRoot) {
122
- const state = readState(projectRoot);
123
- if (!state) return null;
124
- if (await isDaemonAlive(state)) return state;
125
- clearState(projectRoot);
126
- return null;
127
- }
128
- /**
129
- * Resolve the CLI entry point, so the daemon can be spawned as ourselves.
130
- *
131
- * A hidden subcommand rather than a second build entry: under `tsx` this file
132
- * is `src/dev-db/daemon.ts` and in a published CLI it is bundled into
133
- * `dist/index.js`, and in both cases the executable to re-invoke is the one
134
- * already running. `rebase dev` re-runs itself the same way to regenerate the
135
- * SDK types.
136
- */
137
- function resolveCliEntry() {
138
- const argvEntry = process.argv[1];
139
- if (argvEntry && fs.existsSync(argvEntry)) return argvEntry;
140
- return fileURLToPath(new URL("data:video/mp2t;base64,LyoqCiAqIFRoZSBDTEkncyBwdWJsaWMgZW50cnkg4oCUIGRlbGliZXJhdGVseSBzbWFsbC4KICoKICogVGhpcyBmaWxlIHVzZWQgdG8gYGV4cG9ydCAqYCBmcm9tIHNpeHRlZW4gbW9kdWxlczogZXZlcnkgY29tbWFuZCBmdW5jdGlvbiwKICogYGRldGVjdFBhY2thZ2VNYW5hZ2VyYCwgYGZpbmRQcm9qZWN0Um9vdGAsIHRoZSB3aG9sZSBvZiBgY29tbWFuZHMvY2xvdWRgLiBOb25lCiAqIG9mIGl0IHdhcyBpbXBvcnRlZCBieSBhbnl0aGluZywgaW4gdGhpcyByZXBvc2l0b3J5IG9yIHRoZSBjb250cm9sIHBsYW5lLCBhbmQKICogbm9uZSBvZiBpdCB3YXMgbWVhbnQgdG8gYmU6IGBAcmViYXNlcHJvL2NsaWAgaXMgYSBwcm9ncmFtLCBhbmQgYSBwcm9ncmFtJ3MKICogaW50ZXJuYWxzIGJlY29tZSBhbiBBUEkgdGhlIG1vbWVudCB0aGV5IGFyZSBleHBvcnRlZCBhbmQgcHVibGlzaGVkLiBUaGUKICogYmFycmVsIHdhcyB3cml0dGVuIHRoZSB3YXkgYmFycmVscyBnZXQgd3JpdHRlbiDigJQgb25lIGxpbmUgcGVyIGZpbGUg4oCUIGFuZCB0aGUKICogc3VyZmFjZSBpdCBjcmVhdGVkIHdhcyBhbiBhY2NpZGVudCBvZiB0aGF0IGNvbnZlbnRpb24gcmF0aGVyIHRoYW4gYSBkZWNpc2lvbi4KICoKICogVGhyZWUgdGhpbmdzIGFyZSBleHBvcnRlZCwgZWFjaCBmb3IgYSBzdGF0ZWQgcmVhc29uLgogKi8KCi8qKgogKiBXaGF0IGBiaW4vcmViYXNlLmpzYCBjYWxscy4gVGhlIGJpbmFyeSBpbXBvcnRzIGBkaXN0L2luZGV4LmVzLmpzYCBhbmQgaW52b2tlcwogKiBgZW50cnkoKWAsIHNvIHRoaXMgaXMgdGhlIG9uZSBleHBvcnQgdGhlIENMSSBjYW5ub3QgcnVuIHdpdGhvdXQuCiAqLwpleHBvcnQgeyBlbnRyeSB9IGZyb20gIi4vY2xpIjsKCi8qKgogKiBUaGUgbWFuaWZlc3QgYW5kIGJ1bmRsZSBjb250cmFjdHMuCiAqCiAqIEV4cG9ydGVkIGJlY2F1c2UgdmFsaWRhdGluZyB0aGVtIGlzIG5vdCBvbmx5IHRoZSBDTEkncyBqb2I6IGEgY29udHJvbCBwbGFuZQogKiBoYXMgdG8gZGVjaWRlIHdoZXRoZXIgYSBzdWJtaXR0ZWQgYnVuZGxlIGNhbiBydW4gYmVmb3JlIGl0IGRlcGxveXMgaXQsIGFuZCBpdAogKiBtdXN0IHJlYWNoIHRoYXQgdmVyZGljdCB3aXRoIHRoZSBzYW1lIGNvZGUgdGhhdCBwcm9kdWNlZCB0aGUgYXJ0aWZhY3QuCiAqLwpleHBvcnQgKiBmcm9tICIuL21hbmlmZXN0IjsKZXhwb3J0ICogZnJvbSAiLi9idW5kbGUiOwo=", "" + import.meta.url));
141
- }
142
- /**
143
- * How to re-invoke ourselves, which differs between a published CLI and this
144
- * repository.
145
- *
146
- * A published `rebase` is `node bin/rebase.js`, and re-running that is trivial.
147
- * Inside the monorepo the entry is TypeScript, which plain `node` cannot load —
148
- * so the daemon has to be started through the same loader that is running now.
149
- * Getting this wrong fails as a spawn that exits instantly with a syntax error,
150
- * which is why the caller reads `pglite.log` on failure.
151
- */
152
- function resolveSpawn(entry) {
153
- if (!/\.[cm]?ts$/.test(entry)) return {
154
- execPath: process.execPath,
155
- prefixArgs: []
156
- };
157
- return {
158
- execPath: process.execPath,
159
- prefixArgs: ["--import", "tsx"]
160
- };
161
- }
162
- /**
163
- * Ask the running daemon for one additional database, and get its port.
164
- *
165
- * The identity socket doubles as the command channel: the daemon answers
166
- * with its token first, which is how the caller knows it is talking to the
167
- * daemon the state file describes and not to a stranger on a recycled port.
168
- */
169
- function ensureAdditionalDatabase(state, key) {
170
- return new Promise((resolve, reject) => {
171
- const socket = new net.Socket();
172
- let answer = "";
173
- let identified = false;
174
- const fail = (message) => {
175
- socket.removeAllListeners();
176
- socket.destroy();
177
- reject(new Error(message));
178
- };
179
- socket.setTimeout(START_TIMEOUT_MS);
180
- socket.once("timeout", () => fail(`The development database did not answer for "${key}" within ${Math.round(START_TIMEOUT_MS / 1e3)}s.`));
181
- socket.once("error", (err) => fail(`Could not reach the development database for "${key}": ${err.message}`));
182
- socket.on("data", (chunk) => {
183
- answer += chunk.toString("utf8");
184
- let newline = answer.indexOf("\n");
185
- while (newline !== -1) {
186
- const line = answer.slice(0, newline).trim();
187
- answer = answer.slice(newline + 1);
188
- if (!identified) {
189
- if (line !== `rebase-dev-db ${state.token}`) return fail("The process on the development database's port is not the daemon.");
190
- identified = true;
191
- socket.write(`ensure ${key}\n`);
192
- } else if (line.startsWith("ok ")) {
193
- const port = Number(line.slice(3));
194
- socket.removeAllListeners();
195
- socket.destroy();
196
- if (!Number.isInteger(port) || port <= 0) return reject(/* @__PURE__ */ new Error(`The development database answered with an invalid port for "${key}": ${line}`));
197
- return resolve(port);
198
- } else return fail(`The development database could not serve "${key}": ${line.replace(/^error\s*/, "")}`);
199
- newline = answer.indexOf("\n");
200
- }
201
- });
202
- socket.connect(state.identityPort, "127.0.0.1");
203
- });
204
- }
205
- /** Every additional database the caller asked for, as connection strings. */
206
- async function adoptWithAdditional(state, started, keys) {
207
- const additional = {};
208
- for (const key of keys) {
209
- const known = state.databases?.[key];
210
- additional[key] = managedUrl(known ? known.port : await ensureAdditionalDatabase(state, key));
211
- }
212
- return {
213
- ...adopt(state, started),
214
- additional
215
- };
216
- }
217
- /**
218
- * The project's managed database, started if it is not already running.
219
- *
220
- * Safe to call concurrently from several commands: the loser of the race finds
221
- * the winner's state file during its poll and adopts it rather than starting a
222
- * second daemon.
223
- */
224
- async function ensureManagedDatabase(projectRoot, options = {}) {
225
- const keys = options.additionalKeys ?? [];
226
- const existing = await findRunningDaemon(projectRoot);
227
- if (existing) return adoptWithAdditional(existing, false, keys);
228
- fs.mkdirSync(devDbDir(projectRoot), { recursive: true });
229
- ensureGitignore(projectRoot);
230
- if (!acquireStartLock(projectRoot, START_TIMEOUT_MS)) {
231
- const adopted = await waitForDaemon(projectRoot, START_TIMEOUT_MS);
232
- if (adopted) return adoptWithAdditional(adopted, false, keys);
233
- if (!acquireStartLock(projectRoot, 0)) throw new Error(`Another process is starting the development database and did not finish.
234
- Remove ${path.join(devDbDir(projectRoot), "starting.lock")} if nothing is running.`);
235
- }
236
- const port = await findFreePort();
237
- const token = randomBytes(16).toString("hex");
238
- const entry = options.entry ?? resolveCliEntry();
239
- const spawnPlan = options.spawn ?? resolveSpawn(entry);
240
- const args = [
241
- ...spawnPlan.prefixArgs,
242
- entry,
243
- "__dev-db-daemon",
244
- "--project",
245
- projectRoot,
246
- "--port",
247
- String(port),
248
- "--token",
249
- token
250
- ];
251
- if (options.idleTimeoutMs !== void 0) args.push("--idle-timeout", String(options.idleTimeoutMs));
252
- const log = fs.openSync(path.join(devDbDir(projectRoot), "pglite.log"), "a");
253
- const child = spawn(spawnPlan.execPath, args, {
254
- detached: true,
255
- stdio: [
256
- "ignore",
257
- log,
258
- log
259
- ]
260
- });
261
- child.unref();
262
- try {
263
- const deadline = Date.now() + START_TIMEOUT_MS;
264
- let announced = false;
265
- while (Date.now() < deadline) {
266
- const state = readState(projectRoot);
267
- if (state && await isDaemonAlive(state)) return adoptWithAdditional(state, true, keys);
268
- if (!announced && !options.quiet) {
269
- announced = true;
270
- options.onProgress?.("Starting the development database…");
271
- }
272
- if (child.exitCode !== null && child.exitCode !== 0) throw new Error(`The development database failed to start (exit ${child.exitCode}).\n ${path.join(devDbDir(projectRoot), "pglite.log")} ends:\n` + pgliteLogTail(projectRoot));
273
- await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
274
- }
275
- throw new Error(`The development database did not start within ${Math.round(START_TIMEOUT_MS / 1e3)}s.\n ${path.join(devDbDir(projectRoot), "pglite.log")} ends:\n` + pgliteLogTail(projectRoot) + "\n To use your own Postgres instead, set DATABASE_URL or pass --database-url.");
276
- } finally {
277
- releaseStartLock(projectRoot);
278
- }
279
- }
280
- /** Shape a live state record as the caller's result. */
281
- function adopt(state, started) {
282
- return {
283
- url: managedUrl(state.port),
284
- dataDir: state.dataDir,
285
- port: state.port,
286
- pid: state.pid,
287
- started,
288
- additional: {}
289
- };
290
- }
291
- /** Poll until another process publishes a live daemon, or give up. */
292
- async function waitForDaemon(projectRoot, timeoutMs) {
293
- const deadline = Date.now() + timeoutMs;
294
- while (Date.now() < deadline) {
295
- const state = readState(projectRoot);
296
- if (state && await isDaemonAlive(state)) return state;
297
- await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
298
- }
299
- return null;
300
- }
301
- /** Stop the daemon. Returns false when there was nothing running. */
302
- async function stopManagedDatabase(projectRoot) {
303
- const state = await findRunningDaemon(projectRoot);
304
- if (!state) return false;
305
- try {
306
- process.kill(state.pid, "SIGTERM");
307
- } catch {
308
- clearState(projectRoot);
309
- return false;
310
- }
311
- const deadline = Date.now() + 1e4;
312
- while (Date.now() < deadline) {
313
- if (!fs.existsSync(stateFile(projectRoot))) return true;
314
- await new Promise((resolve) => setTimeout(resolve, 100));
315
- }
316
- clearState(projectRoot);
317
- return true;
318
- }
319
- /**
320
- * Stop the daemon and delete the data directory.
321
- *
322
- * Destructive and deliberately not clever: it removes the whole directory
323
- * rather than dropping schemas, because "give me an empty database" is the only
324
- * thing anyone means by it.
325
- */
326
- async function resetManagedDatabase(projectRoot) {
327
- await stopManagedDatabase(projectRoot);
328
- fs.rmSync(dataDir(projectRoot), {
329
- recursive: true,
330
- force: true
331
- });
332
- const dir = devDbDir(projectRoot);
333
- if (fs.existsSync(dir)) {
334
- for (const entry of fs.readdirSync(dir)) if (entry.startsWith("pgdata__")) fs.rmSync(path.join(dir, entry), {
335
- recursive: true,
336
- force: true
337
- });
338
- }
339
- clearState(projectRoot);
340
- }
341
- /**
342
- * Keep the generated directory out of git.
343
- *
344
- * `.rebase/` holds a Postgres data directory and a log. A project that commits
345
- * it will push hundreds of megabytes and a database that only makes sense on
346
- * one machine, so the ignore file is written next to the data rather than
347
- * relying on the project's root `.gitignore` having been updated.
348
- */
349
- function ensureGitignore(projectRoot) {
350
- const target = path.join(devDbDir(projectRoot), ".gitignore");
351
- if (fs.existsSync(target)) return;
352
- fs.writeFileSync(target, "# Generated by `rebase dev` — a local Postgres data directory and its log.\n# Machine-specific and large; never commit it.\n*\n", "utf8");
353
- }
354
- //#endregion
355
- export { resolveCliEntry as a, managedUrl as i, ensureManagedDatabase as n, resolveSpawn as o, findRunningDaemon as r, daemon_exports as t };
356
-
357
- //# sourceMappingURL=daemon-C8p32i-n.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"daemon-C8p32i-n.js","names":[],"sources":["../src/dev-db/daemon.ts"],"sourcesContent":["/**\n * Starting, finding and stopping the managed database, from the caller's side.\n *\n * Every command that needs Postgres calls {@link ensureManagedDatabase} and\n * gets a connection string back. Whether that started a process or found one\n * already running is not the caller's business, which is the point: `rebase\n * db push`, `rebase dev` and `rebase studio` in three terminals must all reach\n * the same database without coordinating, because two processes opening one\n * PGlite data directory would corrupt it.\n *\n * The hard part is not starting the daemon; it is deciding whether the one the\n * state file describes is still there. A pid can be recycled after a reboot and\n * a port can be taken by a stranger, so believing either on its own would let\n * Rebase send a migration somewhere unintended. {@link isDaemonAlive} asks the\n * daemon to identify itself instead.\n */\n\nimport { randomBytes } from \"crypto\";\nimport { type ChildProcess, spawn } from \"child_process\";\nimport fs from \"fs\";\nimport net from \"net\";\nimport path from \"path\";\nimport { fileURLToPath } from \"url\";\n\nimport {\n acquireStartLock,\n clearState,\n type DaemonState,\n dataDir,\n devDbDir,\n findFreePort,\n pidRunning,\n readState,\n releaseStartLock,\n stateFile\n} from \"./state\";\n\n/** How long to wait for a first boot. PGlite runs initdb on an empty data dir. */\nconst START_TIMEOUT_MS = 60_000;\n/** Poll interval while waiting for the daemon to publish its state file. */\nconst POLL_INTERVAL_MS = 150;\n/** How long the identity handshake may take before we call it dead. */\nconst IDENTITY_TIMEOUT_MS = 1_500;\n\nexport interface ManagedDatabase {\n /** Connection string for this project's managed database. */\n url: string;\n /** Where the data lives, for diagnostics and `--reset`. */\n dataDir: string;\n port: number;\n pid: number;\n /** True when this call started the daemon rather than finding it. */\n started: boolean;\n /**\n * Connection strings for the additional databases the caller asked for,\n * by declared key — `database(\"analytics\")` → `analytics`. Each is its\n * own PGlite instance behind the same daemon.\n */\n additional: Record<string, string>;\n}\n\n/**\n * PGlite's built-in superuser and database.\n *\n * Fixed by PGlite rather than chosen here, and asserted by the socket spike:\n * `session_user` comes back as `postgres`.\n */\nconst MANAGED_USER = \"postgres\";\nconst MANAGED_DATABASE = \"postgres\";\n\n/**\n * The DSN for the managed development database.\n *\n * **`sslmode=disable` is not decoration.** PGlite's socket server speaks no TLS,\n * and libpq-family clients try SSL first — so Atlas, which `rebase db push`\n * shells out to, failed the whole push with\n * `pq: SSL is not enabled on the server`. Its remedy box then told the reader to\n * append `sslmode=disable` to `DATABASE_URL`, which on this path is not set at\n * all: `rebase init` leaves it commented out precisely so the managed database\n * is used. The advice was correct and unfollowable.\n *\n * Putting it in the URL fixes every consumer at once — Atlas, `pg`, and anything\n * a driver shells out to later — rather than teaching each one about PGlite.\n */\n\n/**\n * The end of `pglite.log`, for an error message that has to carry its own reason.\n *\n * The daemon writes its output to a file because it is detached — it has to\n * outlive the command that started it. That is right, and it means every\n * startup failure used to be reported as a path: \"see pglite.log for the\n * reason\". On a laptop that is a fair trade. In CI it is not a trade at all,\n * because the runner discards the workspace: the log named in the message no\n * longer exists by the time anybody reads the message, so a failed run says\n * only that something failed.\n *\n * So the tail comes with the error. Bounded, because a log that has been\n * appended to across many runs is not something to paste in full.\n */\nexport function pgliteLogTail(projectRoot: string, lines = 12): string {\n try {\n const contents = fs.readFileSync(path.join(devDbDir(projectRoot), \"pglite.log\"), \"utf8\").trimEnd();\n if (!contents) return \" (pglite.log is empty — the daemon wrote nothing before exiting)\";\n return contents.split(\"\\n\").slice(-lines).map(line => ` ${line}`).join(\"\\n\");\n } catch {\n return \" (pglite.log could not be read)\";\n }\n}\n\nexport function managedUrl(port: number): string {\n return `postgresql://${MANAGED_USER}@127.0.0.1:${port}/${MANAGED_DATABASE}?sslmode=disable`;\n}\n\n/**\n * Ask the process behind a state record to prove it is the one we wrote down.\n *\n * A pid check alone answers \"is *a* process running\", and a port check alone\n * answers \"is *something* listening\" — after a reboot both say yes about\n * strangers. The daemon answers with the token from its own state file, so a\n * match is the only evidence accepted.\n */\nexport function isDaemonAlive(state: DaemonState): Promise<boolean> {\n if (!pidRunning(state.pid)) return Promise.resolve(false);\n\n return new Promise((resolve) => {\n const socket = new net.Socket();\n let answer = \"\";\n const settle = (alive: boolean) => {\n socket.removeAllListeners();\n socket.destroy();\n resolve(alive);\n };\n socket.setTimeout(IDENTITY_TIMEOUT_MS);\n socket.once(\"timeout\", () => settle(false));\n socket.once(\"error\", () => settle(false));\n socket.on(\"data\", (chunk) => {\n answer += chunk.toString(\"utf8\");\n if (answer.includes(\"\\n\")) settle(answer.trim() === `rebase-dev-db ${state.token}`);\n });\n socket.once(\"close\", () => settle(answer.trim() === `rebase-dev-db ${state.token}`));\n socket.connect(state.identityPort, \"127.0.0.1\");\n });\n}\n\n/** The running daemon for this project, or `null`. Never starts anything. */\nexport async function findRunningDaemon(projectRoot: string): Promise<DaemonState | null> {\n const state = readState(projectRoot);\n if (!state) return null;\n if (await isDaemonAlive(state)) return state;\n\n // The record describes something that is not there. Clearing it is the\n // whole recovery: the next ensure call starts a fresh daemon, and leaving\n // it would make every subsequent check pay the identity timeout.\n clearState(projectRoot);\n\n return null;\n}\n\n/**\n * Resolve the CLI entry point, so the daemon can be spawned as ourselves.\n *\n * A hidden subcommand rather than a second build entry: under `tsx` this file\n * is `src/dev-db/daemon.ts` and in a published CLI it is bundled into\n * `dist/index.js`, and in both cases the executable to re-invoke is the one\n * already running. `rebase dev` re-runs itself the same way to regenerate the\n * SDK types.\n */\nexport function resolveCliEntry(): string {\n // `process.argv[1]` is `bin/rebase.js` for a real invocation, which is\n // exactly what should be re-run. Under a test runner it is the runner, so\n // fall back to this module's own directory.\n const argvEntry = process.argv[1];\n if (argvEntry && fs.existsSync(argvEntry)) return argvEntry;\n\n return fileURLToPath(new URL(\"../index.js\", import.meta.url));\n}\n\n/**\n * How to re-invoke ourselves, which differs between a published CLI and this\n * repository.\n *\n * A published `rebase` is `node bin/rebase.js`, and re-running that is trivial.\n * Inside the monorepo the entry is TypeScript, which plain `node` cannot load —\n * so the daemon has to be started through the same loader that is running now.\n * Getting this wrong fails as a spawn that exits instantly with a syntax error,\n * which is why the caller reads `pglite.log` on failure.\n */\nexport function resolveSpawn(entry: string): { execPath: string; prefixArgs: string[] } {\n if (!/\\.[cm]?ts$/.test(entry)) return { execPath: process.execPath, prefixArgs: [] };\n\n // Node can load TypeScript when tsx is registered as an import hook. tsx is\n // already a dependency of this package, because `rebase dev` runs the\n // backend through it.\n return { execPath: process.execPath, prefixArgs: [\"--import\", \"tsx\"] };\n}\n\nexport interface EnsureOptions {\n /** Silence the \"starting…\" progress line. */\n quiet?: boolean;\n /** Milliseconds of inactivity before the daemon exits. 0 disables. */\n idleTimeoutMs?: number;\n /** Where progress goes. Injected for tests. */\n onProgress?: (message: string) => void;\n /** Override how the daemon process is launched. For tests. */\n spawn?: { execPath: string; prefixArgs: string[] };\n /**\n * Override the CLI entry to re-invoke. For tests.\n *\n * Necessary because under a test runner `process.argv[1]` is the runner\n * itself, which exists and is therefore accepted by {@link resolveCliEntry}\n * — spawning vitest with `__dev-db-daemon` rather than the CLI.\n */\n entry?: string;\n /**\n * Keys of the additional databases the project declares, beyond the\n * default. Each is served by the daemon on request, so a project that\n * adds `database(\"analytics\")` while the daemon is running gets it\n * without a restart.\n */\n additionalKeys?: readonly string[];\n}\n\n/**\n * Ask the running daemon for one additional database, and get its port.\n *\n * The identity socket doubles as the command channel: the daemon answers\n * with its token first, which is how the caller knows it is talking to the\n * daemon the state file describes and not to a stranger on a recycled port.\n */\nfunction ensureAdditionalDatabase(state: DaemonState, key: string): Promise<number> {\n return new Promise((resolve, reject) => {\n const socket = new net.Socket();\n let answer = \"\";\n let identified = false;\n const fail = (message: string) => {\n socket.removeAllListeners();\n socket.destroy();\n reject(new Error(message));\n };\n socket.setTimeout(START_TIMEOUT_MS);\n socket.once(\"timeout\", () => fail(`The development database did not answer for \"${key}\" within ${Math.round(START_TIMEOUT_MS / 1000)}s.`));\n socket.once(\"error\", (err) => fail(`Could not reach the development database for \"${key}\": ${err.message}`));\n socket.on(\"data\", (chunk) => {\n answer += chunk.toString(\"utf8\");\n let newline = answer.indexOf(\"\\n\");\n while (newline !== -1) {\n const line = answer.slice(0, newline).trim();\n answer = answer.slice(newline + 1);\n if (!identified) {\n if (line !== `rebase-dev-db ${state.token}`) return fail(\"The process on the development database's port is not the daemon.\");\n identified = true;\n socket.write(`ensure ${key}\\n`);\n } else if (line.startsWith(\"ok \")) {\n const port = Number(line.slice(3));\n socket.removeAllListeners();\n socket.destroy();\n if (!Number.isInteger(port) || port <= 0) return reject(new Error(`The development database answered with an invalid port for \"${key}\": ${line}`));\n return resolve(port);\n } else {\n return fail(`The development database could not serve \"${key}\": ${line.replace(/^error\\s*/, \"\")}`);\n }\n newline = answer.indexOf(\"\\n\");\n }\n });\n socket.connect(state.identityPort, \"127.0.0.1\");\n });\n}\n\n/** Every additional database the caller asked for, as connection strings. */\nasync function adoptWithAdditional(state: DaemonState, started: boolean, keys: readonly string[]): Promise<ManagedDatabase> {\n const additional: Record<string, string> = {};\n for (const key of keys) {\n const known = state.databases?.[key];\n const port = known ? known.port : await ensureAdditionalDatabase(state, key);\n additional[key] = managedUrl(port);\n }\n return { ...adopt(state, started), additional };\n}\n\n/**\n * The project's managed database, started if it is not already running.\n *\n * Safe to call concurrently from several commands: the loser of the race finds\n * the winner's state file during its poll and adopts it rather than starting a\n * second daemon.\n */\nexport async function ensureManagedDatabase(\n projectRoot: string,\n options: EnsureOptions = {}\n): Promise<ManagedDatabase> {\n const keys = options.additionalKeys ?? [];\n const existing = await findRunningDaemon(projectRoot);\n if (existing) return adoptWithAdditional(existing, false, keys);\n\n fs.mkdirSync(devDbDir(projectRoot), { recursive: true });\n ensureGitignore(projectRoot);\n\n // Exactly one process may spawn a daemon. Without this, `rebase dev` and\n // `rebase db push` started in the same second both see no state file, both\n // spawn, and two processes open one PGlite data directory — the corruption\n // the single-daemon design exists to prevent.\n if (!acquireStartLock(projectRoot, START_TIMEOUT_MS)) {\n const adopted = await waitForDaemon(projectRoot, START_TIMEOUT_MS);\n if (adopted) return adoptWithAdditional(adopted, false, keys);\n\n // The holder gave up or died without publishing. Falling through to\n // start one ourselves is better than failing: the lock is stale by now,\n // so the next acquire will break it.\n if (!acquireStartLock(projectRoot, 0)) {\n throw new Error(\n \"Another process is starting the development database and did not finish.\\n\" +\n ` Remove ${path.join(devDbDir(projectRoot), \"starting.lock\")} if nothing is running.`\n );\n }\n }\n\n const port = await findFreePort();\n const token = randomBytes(16).toString(\"hex\");\n const entry = options.entry ?? resolveCliEntry();\n\n const spawnPlan = options.spawn ?? resolveSpawn(entry);\n const args = [\n ...spawnPlan.prefixArgs,\n entry,\n \"__dev-db-daemon\",\n \"--project\", projectRoot,\n \"--port\", String(port),\n \"--token\", token\n ];\n if (options.idleTimeoutMs !== undefined) {\n args.push(\"--idle-timeout\", String(options.idleTimeoutMs));\n }\n\n const log = fs.openSync(path.join(devDbDir(projectRoot), \"pglite.log\"), \"a\");\n const child: ChildProcess = spawn(spawnPlan.execPath, args, {\n // Detached with no stdin and its output on a file: the daemon must\n // survive the command that started it, and must not hold the terminal\n // open when that command exits.\n detached: true,\n stdio: [\"ignore\", log, log]\n });\n child.unref();\n\n try {\n const deadline = Date.now() + START_TIMEOUT_MS;\n let announced = false;\n while (Date.now() < deadline) {\n const state = readState(projectRoot);\n if (state && (await isDaemonAlive(state))) return adoptWithAdditional(state, true, keys);\n\n if (!announced && !options.quiet) {\n announced = true;\n options.onProgress?.(\"Starting the development database…\");\n }\n\n // A daemon that died on startup will never publish a state file, and\n // waiting the full minute for that is a bad way to learn it. The log\n // is the only place the reason exists.\n if (child.exitCode !== null && child.exitCode !== 0) {\n throw new Error(\n `The development database failed to start (exit ${child.exitCode}).\\n` +\n ` ${path.join(devDbDir(projectRoot), \"pglite.log\")} ends:\\n` +\n pgliteLogTail(projectRoot)\n );\n }\n\n await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));\n }\n\n throw new Error(\n `The development database did not start within ${Math.round(START_TIMEOUT_MS / 1000)}s.\\n` +\n ` ${path.join(devDbDir(projectRoot), \"pglite.log\")} ends:\\n` +\n pgliteLogTail(projectRoot) + \"\\n\" +\n \" To use your own Postgres instead, set DATABASE_URL or pass --database-url.\"\n );\n } finally {\n // Released whether we succeeded, timed out or threw. A lock left behind\n // by a crash is broken by age, but only after a full timeout — which a\n // developer should never have to wait out.\n releaseStartLock(projectRoot);\n }\n}\n\n/** Shape a live state record as the caller's result. */\nfunction adopt(state: DaemonState, started: boolean): ManagedDatabase {\n return {\n url: managedUrl(state.port),\n dataDir: state.dataDir,\n port: state.port,\n pid: state.pid,\n started,\n additional: {}\n };\n}\n\n/** Poll until another process publishes a live daemon, or give up. */\nasync function waitForDaemon(projectRoot: string, timeoutMs: number): Promise<DaemonState | null> {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n const state = readState(projectRoot);\n if (state && (await isDaemonAlive(state))) return state;\n await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));\n }\n\n return null;\n}\n\n/** Stop the daemon. Returns false when there was nothing running. */\nexport async function stopManagedDatabase(projectRoot: string): Promise<boolean> {\n const state = await findRunningDaemon(projectRoot);\n if (!state) return false;\n\n try {\n process.kill(state.pid, \"SIGTERM\");\n } catch {\n clearState(projectRoot);\n\n return false;\n }\n\n // Wait for it to clear its own state file, which is how it says it closed\n // the data directory cleanly. A hard kill here would risk the WAL.\n const deadline = Date.now() + 10_000;\n while (Date.now() < deadline) {\n if (!fs.existsSync(stateFile(projectRoot))) return true;\n await new Promise((resolve) => setTimeout(resolve, 100));\n }\n\n clearState(projectRoot);\n\n return true;\n}\n\n/**\n * Stop the daemon and delete the data directory.\n *\n * Destructive and deliberately not clever: it removes the whole directory\n * rather than dropping schemas, because \"give me an empty database\" is the only\n * thing anyone means by it.\n */\nexport async function resetManagedDatabase(projectRoot: string): Promise<void> {\n await stopManagedDatabase(projectRoot);\n fs.rmSync(dataDir(projectRoot), { recursive: true, force: true });\n // Every additional database too: `pgdata__<key>` beside `pgdata`. \"Give\n // me an empty database\" means all of them — a reset that emptied the\n // default and kept the analytics rows would be a surprise on first read.\n const dir = devDbDir(projectRoot);\n if (fs.existsSync(dir)) {\n for (const entry of fs.readdirSync(dir)) {\n if (entry.startsWith(\"pgdata__\")) fs.rmSync(path.join(dir, entry), { recursive: true, force: true });\n }\n }\n clearState(projectRoot);\n}\n\n/**\n * Keep the generated directory out of git.\n *\n * `.rebase/` holds a Postgres data directory and a log. A project that commits\n * it will push hundreds of megabytes and a database that only makes sense on\n * one machine, so the ignore file is written next to the data rather than\n * relying on the project's root `.gitignore` having been updated.\n */\nfunction ensureGitignore(projectRoot: string): void {\n const target = path.join(devDbDir(projectRoot), \".gitignore\");\n if (fs.existsSync(target)) return;\n fs.writeFileSync(\n target,\n \"# Generated by `rebase dev` — a local Postgres data directory and its log.\\n\" +\n \"# Machine-specific and large; never commit it.\\n*\\n\",\n \"utf8\"\n );\n}\n"],"mappings":"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"}