@rebasepro/server-postgres 0.25.0 → 0.26.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 (144) hide show
  1. package/dist/{BranchService-iXXZeVYD.js → BranchService-CWBnDhcv.js} +88 -12
  2. package/dist/BranchService-CWBnDhcv.js.map +1 -0
  3. package/dist/PostgresBackendDriver.d.ts +13 -0
  4. package/dist/PostgresBootstrapper.d.ts +7 -0
  5. package/dist/auth/services.d.ts +8 -1
  6. package/dist/auth/verify-auth-tables.d.ts +21 -0
  7. package/dist/backup/backup-cli.d.ts +40 -3
  8. package/dist/backup/backup-service.d.ts +11 -0
  9. package/dist/{backup-service-Bb39plam.js → backup-cli-PoRG-zEq.js} +777 -10
  10. package/dist/backup-cli-PoRG-zEq.js.map +1 -0
  11. package/dist/{boot-ddl-s_3Hkz94.js → boot-ddl-EsgqDkx0.js} +3 -3
  12. package/dist/{boot-ddl-s_3Hkz94.js.map → boot-ddl-EsgqDkx0.js.map} +1 -1
  13. package/dist/branch-argv.d.ts +15 -0
  14. package/dist/branch-diff-CxE4VlxT.js +1396 -0
  15. package/dist/branch-diff-CxE4VlxT.js.map +1 -0
  16. package/dist/branch-diff-report-DpvVf7hR.js +48 -0
  17. package/dist/branch-diff-report-DpvVf7hR.js.map +1 -0
  18. package/dist/branching/branch-diff-report.d.ts +7 -0
  19. package/dist/branching/branch-diff.d.ts +115 -0
  20. package/dist/{cli-console-DQ_K-sdM.js → cli-console-f0k6_TsJ.js} +41 -9
  21. package/dist/cli-console-f0k6_TsJ.js.map +1 -0
  22. package/dist/cli-console.d.ts +17 -1
  23. package/dist/{cli-errors-DwQnNATs.js → cli-errors-DsYyNYzd.js} +90 -11
  24. package/dist/cli-errors-DsYyNYzd.js.map +1 -0
  25. package/dist/cli-errors.d.ts +14 -0
  26. package/dist/cli-flags.d.ts +51 -0
  27. package/dist/{cli-prompt-egtNKkZl.js → cli-prompt-DXG0yk-h.js} +39 -4
  28. package/dist/{cli-prompt-egtNKkZl.js.map → cli-prompt-DXG0yk-h.js.map} +1 -1
  29. package/dist/cli.js +121 -271
  30. package/dist/cli.js.map +1 -1
  31. package/dist/{collections-schema-version-T6cGvrDi.js → collections-schema-version-D7bDI0PJ.js} +13 -9
  32. package/dist/collections-schema-version-D7bDI0PJ.js.map +1 -0
  33. package/dist/{column-plan-helpers-D_V4oaZh.js → column-plan-helpers-IG6uFb1s.js} +2 -2
  34. package/dist/{column-plan-helpers-D_V4oaZh.js.map → column-plan-helpers-IG6uFb1s.js.map} +1 -1
  35. package/dist/{connection-C938nR3E.js → connection-czvI7TZL.js} +57 -10
  36. package/dist/connection-czvI7TZL.js.map +1 -0
  37. package/dist/{databasePoolManager-Yqdw14wY.js → databasePoolManager-3stMNFL-.js} +4 -6
  38. package/dist/databasePoolManager-3stMNFL-.js.map +1 -0
  39. package/dist/db-command-line.d.ts +14 -0
  40. package/dist/db-subcommands.d.ts +11 -0
  41. package/dist/{deferred-index-build-Cis_HoGn.js → deferred-index-build-CqaOv1Xx.js} +8 -8
  42. package/dist/{deferred-index-build-Cis_HoGn.js.map → deferred-index-build-CqaOv1Xx.js.map} +1 -1
  43. package/dist/{doctor-DvJH-IQG.js → doctor-DW_mlTzz.js} +186 -42
  44. package/dist/doctor-DW_mlTzz.js.map +1 -0
  45. package/dist/{ensure-collection-policies-CCqayoUz.js → ensure-collection-policies-DLWgabQ2.js} +13 -10
  46. package/dist/ensure-collection-policies-DLWgabQ2.js.map +1 -0
  47. package/dist/{ensure-collection-tables-DvvImXDe.js → ensure-collection-tables-CFIRq8ni.js} +9 -9
  48. package/dist/{ensure-collection-tables-DvvImXDe.js.map → ensure-collection-tables-CFIRq8ni.js.map} +1 -1
  49. package/dist/{ensure-tables-Dpgwx5sy.js → ensure-tables-4nA-tfwi.js} +5 -5
  50. package/dist/{ensure-tables-Dpgwx5sy.js.map → ensure-tables-4nA-tfwi.js.map} +1 -1
  51. package/dist/{enum-labels-kept-CUlUlRkO.js → enum-labels-kept-PcPW65xy.js} +3 -3
  52. package/dist/{enum-labels-kept-CUlUlRkO.js.map → enum-labels-kept-PcPW65xy.js.map} +1 -1
  53. package/dist/{generate-drizzle-schema-C3EAP9HE.js → generate-drizzle-schema-DC0BOUpn.js} +3 -3
  54. package/dist/{generate-drizzle-schema-C3EAP9HE.js.map → generate-drizzle-schema-DC0BOUpn.js.map} +1 -1
  55. package/dist/{generate-drizzle-schema-logic-DA3zM8zc.js → generate-drizzle-schema-logic-CPzCRD-8.js} +4 -4
  56. package/dist/{generate-drizzle-schema-logic-DA3zM8zc.js.map → generate-drizzle-schema-logic-CPzCRD-8.js.map} +1 -1
  57. package/dist/{generate-files-ClmOUHXj.js → generate-files-BP-4W_gm.js} +6 -6
  58. package/dist/{generate-files-ClmOUHXj.js.map → generate-files-BP-4W_gm.js.map} +1 -1
  59. package/dist/{generate-next-step-DCeS4LrJ.js → generate-next-step-DgK5BlBn.js} +2 -2
  60. package/dist/{generate-next-step-DCeS4LrJ.js.map → generate-next-step-DgK5BlBn.js.map} +1 -1
  61. package/dist/{generate-postgres-ddl-logic-4eUP4t8w.js → generate-postgres-ddl-logic-DPl1oH_z.js} +4 -4
  62. package/dist/{generate-postgres-ddl-logic-4eUP4t8w.js.map → generate-postgres-ddl-logic-DPl1oH_z.js.map} +1 -1
  63. package/dist/generated-schema-diff-eZCklRZQ.js +1025 -0
  64. package/dist/generated-schema-diff-eZCklRZQ.js.map +1 -0
  65. package/dist/{generated-schema-staleness-FlxM2i4i.js → generated-schema-staleness-BvNF3i-s.js} +2 -2
  66. package/dist/{generated-schema-staleness-FlxM2i4i.js.map → generated-schema-staleness-BvNF3i-s.js.map} +1 -1
  67. package/dist/history/HistoryService.d.ts +7 -0
  68. package/dist/index.d.ts +21 -0
  69. package/dist/index.es.js +288 -11
  70. package/dist/index.es.js.map +1 -1
  71. package/dist/{introspect-db-logic-CI5CU_n0.js → introspect-db-logic-DgQVzccW.js} +2 -2
  72. package/dist/{introspect-db-logic-CI5CU_n0.js.map → introspect-db-logic-DgQVzccW.js.map} +1 -1
  73. package/dist/{rls-bootstrap-sql-Bd-hJk08.js → migrate-baseline-B-2Ef4Mv.js} +718 -41
  74. package/dist/migrate-baseline-B-2Ef4Mv.js.map +1 -0
  75. package/dist/migrate-policy-stamps-BQtFHApx.js +89 -0
  76. package/dist/migrate-policy-stamps-BQtFHApx.js.map +1 -0
  77. package/dist/{open-rebase-BKA7Nyoe.js → open-rebase-C8jQ6s5a.js} +324 -1047
  78. package/dist/open-rebase-C8jQ6s5a.js.map +1 -0
  79. package/dist/open-rebase.d.ts +9 -1
  80. package/dist/{pg-error-utils-Bv5n2PLn.js → pg-error-utils-DhiUTlVx.js} +2 -2
  81. package/dist/{pg-error-utils-Bv5n2PLn.js.map → pg-error-utils-DhiUTlVx.js.map} +1 -1
  82. package/dist/{plan-schema-CXOucCw-.js → plan-schema-Czf3q4-Q.js} +4 -4
  83. package/dist/{plan-schema-CXOucCw-.js.map → plan-schema-Czf3q4-Q.js.map} +1 -1
  84. package/dist/{policy-drift-vm0BcvcE.js → policy-drift-X4A-xAzv.js} +3 -3
  85. package/dist/{policy-drift-vm0BcvcE.js.map → policy-drift-X4A-xAzv.js.map} +1 -1
  86. package/dist/pool-disconnects.d.ts +28 -0
  87. package/dist/{push-index-prebuild-DIequ4bV.js → push-index-prebuild-UTvB5pqH.js} +4 -4
  88. package/dist/{push-index-prebuild-DIequ4bV.js.map → push-index-prebuild-UTvB5pqH.js.map} +1 -1
  89. package/dist/push-kept-defaults-BGOlOqRQ.js +137 -0
  90. package/dist/push-kept-defaults-BGOlOqRQ.js.map +1 -0
  91. package/dist/{push-legacy-renames-BqJut4F3.js → push-legacy-renames-BwaKi8xF.js} +4 -4
  92. package/dist/{push-legacy-renames-BqJut4F3.js.map → push-legacy-renames-BwaKi8xF.js.map} +1 -1
  93. package/dist/{push-policy-plan-CvX8hiPb.js → push-policy-plan-DQ2tWceD.js} +5 -5
  94. package/dist/{push-policy-plan-CvX8hiPb.js.map → push-policy-plan-DQ2tWceD.js.map} +1 -1
  95. package/dist/{render-ddl-CQNwZdse.js → render-ddl-4v88y-Gv.js} +4 -4
  96. package/dist/{render-ddl-CQNwZdse.js.map → render-ddl-4v88y-Gv.js.map} +1 -1
  97. package/dist/{rls-enforcement-D7P4-ZYX.js → rls-enforcement-BOHuHVlc.js} +43 -4
  98. package/dist/rls-enforcement-BOHuHVlc.js.map +1 -0
  99. package/dist/schema/destructive-sql.d.ts +16 -0
  100. package/dist/schema/doctor-cli.js +5 -5
  101. package/dist/schema/doctor.d.ts +20 -2
  102. package/dist/schema/ensure-collection-policies.d.ts +4 -1
  103. package/dist/schema/generate-drizzle-schema.js +1 -1
  104. package/dist/schema/generate-postgres-ddl.js +2 -2
  105. package/dist/schema/generated-schema-diff.d.ts +21 -0
  106. package/dist/schema/introspect-db.js +5 -6
  107. package/dist/schema/introspect-db.js.map +1 -1
  108. package/dist/schema/migrate-policy-stamps.d.ts +23 -0
  109. package/dist/schema/migration-freshness.d.ts +69 -0
  110. package/dist/schema/push-kept-defaults.d.ts +57 -0
  111. package/dist/schema/push-policy-plan.d.ts +5 -0
  112. package/dist/schema/rls-bootstrap-sql.d.ts +47 -10
  113. package/dist/security/rls-enforcement.d.ts +18 -0
  114. package/dist/services/BranchService.d.ts +20 -2
  115. package/dist/services/FetchService.d.ts +12 -0
  116. package/dist/services/channel-presence.d.ts +3 -1
  117. package/dist/services/nested-path.d.ts +16 -0
  118. package/dist/services/pg-notify-listener.d.ts +2 -0
  119. package/dist/services/realtimeService.d.ts +18 -1
  120. package/dist/{source-Bv5KPeIx.js → source-fZb92pN8.js} +2 -2
  121. package/dist/{source-Bv5KPeIx.js.map → source-fZb92pN8.js.map} +1 -1
  122. package/dist/{vector-index-BFhyEaiz.js → vector-index-DsQ3V2d2.js} +2 -2
  123. package/dist/{vector-index-BFhyEaiz.js.map → vector-index-DsQ3V2d2.js.map} +1 -1
  124. package/dist/{websocket-MC0zTBar.js → websocket-CKYc3V4N.js} +56 -14
  125. package/dist/websocket-CKYc3V4N.js.map +1 -0
  126. package/package.json +7 -7
  127. package/dist/BranchService-iXXZeVYD.js.map +0 -1
  128. package/dist/backup-cli-B2CAn9fR.js +0 -647
  129. package/dist/backup-cli-B2CAn9fR.js.map +0 -1
  130. package/dist/backup-service-Bb39plam.js.map +0 -1
  131. package/dist/cli-console-DQ_K-sdM.js.map +0 -1
  132. package/dist/cli-errors-DwQnNATs.js.map +0 -1
  133. package/dist/cli-flags-D-b0qL9T.js +0 -206
  134. package/dist/cli-flags-D-b0qL9T.js.map +0 -1
  135. package/dist/collections-schema-version-T6cGvrDi.js.map +0 -1
  136. package/dist/connection-C938nR3E.js.map +0 -1
  137. package/dist/databasePoolManager-Yqdw14wY.js.map +0 -1
  138. package/dist/doctor-DvJH-IQG.js.map +0 -1
  139. package/dist/ensure-collection-policies-CCqayoUz.js.map +0 -1
  140. package/dist/open-rebase-BKA7Nyoe.js.map +0 -1
  141. package/dist/rls-bootstrap-sql-Bd-hJk08.js.map +0 -1
  142. package/dist/rls-enforcement-D7P4-ZYX.js.map +0 -1
  143. package/dist/rolldown-runtime-V7rFbMRe.js +0 -39
  144. package/dist/websocket-MC0zTBar.js.map +0 -1
@@ -1,6 +1,7 @@
1
1
  import { createRequire as __createRequire } from "module";
2
2
  __createRequire(import.meta.url);
3
3
  import { sql } from "drizzle-orm";
4
+ import { revokeInternalTableSql } from "@rebasepro/common";
4
5
  //#region src/schema/collections-schema-version.ts
5
6
  /**
6
7
  * The collections schema version, stamped into the database.
@@ -60,14 +61,17 @@ async function readCollectionsSchemaVersion(db, metaSchema) {
60
61
  */
61
62
  async function stampCollectionsSchemaVersion(db, metaSchema, version) {
62
63
  const qualified = `"${metaSchema}"."schema_meta"`;
63
- await db.execute(sql`CREATE SCHEMA IF NOT EXISTS ${sql.identifier(metaSchema)}`);
64
- await db.execute(sql`
65
- CREATE TABLE IF NOT EXISTS ${sql.raw(qualified)} (
66
- key TEXT PRIMARY KEY,
67
- value TEXT NOT NULL,
68
- updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL
69
- )
70
- `);
64
+ if (!(await db.execute(sql`SELECT to_regclass(${qualified}) IS NOT NULL AS present`)).rows[0]?.present) {
65
+ await db.execute(sql`CREATE SCHEMA IF NOT EXISTS ${sql.identifier(metaSchema)}`);
66
+ await db.execute(sql`
67
+ CREATE TABLE IF NOT EXISTS ${sql.raw(qualified)} (
68
+ key TEXT PRIMARY KEY,
69
+ value TEXT NOT NULL,
70
+ updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL
71
+ )
72
+ `);
73
+ await db.execute(sql.raw(revokeInternalTableSql(metaSchema, "schema_meta")));
74
+ }
71
75
  await db.execute(sql`
72
76
  INSERT INTO ${sql.raw(qualified)} (key, value)
73
77
  VALUES (${VERSION_KEY}, ${version})
@@ -77,4 +81,4 @@ async function stampCollectionsSchemaVersion(db, metaSchema, version) {
77
81
  //#endregion
78
82
  export { readCollectionsSchemaVersion, stampCollectionsSchemaVersion };
79
83
 
80
- //# sourceMappingURL=collections-schema-version-T6cGvrDi.js.map
84
+ //# sourceMappingURL=collections-schema-version-D7bDI0PJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collections-schema-version-D7bDI0PJ.js","names":[],"sources":["../src/schema/collections-schema-version.ts"],"sourcesContent":["/**\n * The collections schema version, stamped into the database.\n *\n * Deliberately the same table shape, the same key/value convention and the same\n * \"unstamped is null, and null is not an error\" rule as `auth/schema-version.ts`.\n * They answer the same kind of question — \"was this database provisioned by\n * something that agrees with me\" — and inventing a second convention for the\n * second one would be a second thing to find, back up and reason about.\n *\n * It does *not* follow the auth stamp's schema. `resolveAuthSchema` tracks the\n * users collection, so a project that puts its users in a custom schema moves\n * the auth stamp with them — correct there, because that stamp describes those\n * tables. This one describes the collections as a whole and belongs with the\n * runtime's own internal state, which is always `rebase`. Usually they land in\n * the same table anyway; when they do not, that is the reason.\n *\n * The value is opaque here on purpose. It is a hash produced by\n * `computeSchemaVersion` in `@rebasepro/types`, and this module's only job is to\n * put a string in and take the same string out. Comparing them, and deciding\n * what a difference means, is `boot/schema-stamp.ts` in the runtime — where it\n * can be tested without a database.\n *\n * Unlike the auth version, this is **not** ordered. The auth stamp is an integer\n * whose comparison direction carries meaning (a database newer than the runtime\n * is unrecoverable, older is the ordinary upgrade path). A schema hash has no\n * order at all: it can say the two disagree and never which is ahead. That is a\n * deliberate limit, and the reason the runtime's response to a mismatch is to\n * describe it rather than to decide who is wrong.\n */\nimport { sql } from \"drizzle-orm\";\nimport type { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport { revokeInternalTableSql } from \"@rebasepro/common\";\n\n/** Key under which the version is stored in the meta table. */\nconst VERSION_KEY = \"collections_schema_version\";\n\n/**\n * Anything that can run SQL for us.\n *\n * Narrower than `NodePgDatabase` so the provisioning handle — which is what the\n * boot path actually has in hand — satisfies it without a cast at every call\n * site.\n */\nexport interface SchemaMetaQueryable {\n execute(query: unknown): Promise<{ rows: Record<string, unknown>[] }>;\n}\n\n/**\n * Read the stamped version, or `null` when this database has never been stamped.\n *\n * `to_regclass` rather than a plain `SELECT` so a missing schema or table is a\n * `null` instead of a thrown 42P01 — the overwhelmingly common case on a fresh\n * database is that neither exists yet, and that is not news.\n */\nexport async function readCollectionsSchemaVersion(\n db: SchemaMetaQueryable | NodePgDatabase,\n metaSchema: string\n): Promise<string | null> {\n const qualified = `\"${metaSchema}\".\"schema_meta\"`;\n const exists = await (db as SchemaMetaQueryable).execute(\n sql`SELECT to_regclass(${qualified}) IS NOT NULL AS present`\n );\n if (!(exists.rows[0] as { present: boolean } | undefined)?.present) return null;\n\n const result = await (db as SchemaMetaQueryable).execute(sql`\n SELECT value FROM ${sql.raw(qualified)} WHERE key = ${VERSION_KEY}\n `);\n const raw = (result.rows[0] as { value: string } | undefined)?.value;\n\n // An empty string is not a version anybody computed, and treating it as one\n // would make every process disagree with the database forever. Unstamped.\n return raw === undefined || raw.trim() === \"\" ? null : raw;\n}\n\n/**\n * Record the version this runtime just applied.\n *\n * Creates the meta table if the auth stamp has not already — the two are\n * independent halves of one boot and either may run first, so neither can assume\n * the table is there. `CREATE TABLE IF NOT EXISTS` is not atomic against a\n * concurrent identical statement, which is exactly the race a split deployment\n * arranges; it is tolerated here for the same reason it is elsewhere in the boot\n * path, and the caller treats any failure as a warning rather than a fatal.\n */\nexport async function stampCollectionsSchemaVersion(\n db: SchemaMetaQueryable | NodePgDatabase,\n metaSchema: string,\n version: string\n): Promise<void> {\n const qualified = `\"${metaSchema}\".\"schema_meta\"`;\n const exists = await (db as SchemaMetaQueryable).execute(\n sql`SELECT to_regclass(${qualified}) IS NOT NULL AS present`\n );\n if (!(exists.rows[0] as { present: boolean } | undefined)?.present) {\n await (db as SchemaMetaQueryable).execute(sql`CREATE SCHEMA IF NOT EXISTS ${sql.identifier(metaSchema)}`);\n await (db as SchemaMetaQueryable).execute(sql`\n CREATE TABLE IF NOT EXISTS ${sql.raw(qualified)} (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL,\n updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL\n )\n `);\n // On a backend without auth this stamp is what creates the table, and\n // it runs after the boot's last sweep of the request role's grants —\n // so the table inherited the schema-wide DML grant until the next\n // boot's sweep took it back. A request role that can rewrite the auth\n // version stamp can make that boot refuse. Taken back here, by its\n // creator, as every other internal table is.\n await (db as SchemaMetaQueryable).execute(sql.raw(revokeInternalTableSql(metaSchema, \"schema_meta\")));\n }\n await (db as SchemaMetaQueryable).execute(sql`\n INSERT INTO ${sql.raw(qualified)} (key, value)\n VALUES (${VERSION_KEY}, ${version})\n ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = NOW()\n `);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCA,IAAM,cAAc;;;;;;;;AAoBpB,eAAsB,6BAClB,IACA,YACsB;CACtB,MAAM,YAAY,IAAI,WAAW;CAIjC,IAAI,EAAE,MAHgB,GAA2B,QAC7C,GAAG,sBAAsB,UAAU,yBACvC,EAAA,CACa,KAAK,EAAE,EAAuC,SAAS,OAAO;CAK3E,MAAM,OAAO,MAHS,GAA2B,QAAQ,GAAG;4BACpC,IAAI,IAAI,SAAS,EAAE,eAAe,YAAY;KACrE,EAAA,CACmB,KAAK,EAAE,EAAoC;CAI/D,OAAO,QAAQ,KAAA,KAAa,IAAI,KAAK,MAAM,KAAK,OAAO;AAC3D;;;;;;;;;;;AAYA,eAAsB,8BAClB,IACA,YACA,SACa;CACb,MAAM,YAAY,IAAI,WAAW;CAIjC,IAAI,EAAE,MAHgB,GAA2B,QAC7C,GAAG,sBAAsB,UAAU,yBACvC,EAAA,CACa,KAAK,EAAE,EAAuC,SAAS;EAChE,MAAO,GAA2B,QAAQ,GAAG,+BAA+B,IAAI,WAAW,UAAU,GAAG;EACxG,MAAO,GAA2B,QAAQ,GAAG;yCACZ,IAAI,IAAI,SAAS,EAAE;;;;;SAKnD;EAOD,MAAO,GAA2B,QAAQ,IAAI,IAAI,uBAAuB,YAAY,aAAa,CAAC,CAAC;CACxG;CACA,MAAO,GAA2B,QAAQ,GAAG;sBAC3B,IAAI,IAAI,SAAS,EAAE;kBACvB,YAAY,IAAI,QAAQ;;KAErC;AACL"}
@@ -1,6 +1,6 @@
1
1
  import { createRequire as __createRequire } from "module";
2
2
  __createRequire(import.meta.url);
3
- import { n as __exportAll } from "./rolldown-runtime-V7rFbMRe.js";
3
+ import { i as __exportAll } from "./cli-prompt-DXG0yk-h.js";
4
4
  import { toSnakeCase } from "@rebasepro/utils";
5
5
  //#region src/schema/column-plan-helpers.ts
6
6
  var column_plan_helpers_exports = /* @__PURE__ */ __exportAll({
@@ -154,4 +154,4 @@ var sqlExpressionText = (value) => value.startsWith("sql`") && value.endsWith("`
154
154
  //#endregion
155
155
  export { getPrimaryKeyName as a, isIdProperty as c, sqlExpressionText as d, getPrimaryKeyColumn as i, primaryKeyPropertyNames as l, declaresEnumType as n, getPrimaryKeyProp as o, enumLabelsOf as r, idColumnDefault as s, column_plan_helpers_exports as t, resolveColumnName as u };
156
156
 
157
- //# sourceMappingURL=column-plan-helpers-D_V4oaZh.js.map
157
+ //# sourceMappingURL=column-plan-helpers-IG6uFb1s.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"column-plan-helpers-D_V4oaZh.js","names":[],"sources":["../src/schema/column-plan-helpers.ts"],"sourcesContent":["/**\n * The questions about a property that the schema planner asks first.\n *\n * What is the primary key, what is this column called, what does an id default\n * to, what labels does this enum have. `schema/plan/plan-schema.ts` is the only\n * caller — it is the only reader of a `Property` — and these live beside it\n * rather than inside it because they are also where the *refusals* are, and a\n * refusal wants to be reachable and testable on its own.\n *\n * They were extracted while there were still three emitters, each carrying its\n * own verbatim copy of all four rules until one of them was edited: the audit\n * that produced this module counted twelve open disagreements and found that a\n * third of the commits touching those files were re-synchronisations. The\n * emitters are renderers now and there is nothing left to keep in step, but the\n * questions are still questions about a property rather than about a plan, so\n * this is where they belong.\n *\n * Everything here is pure and synchronous, and it throws rather than guesses:\n * an enum with no values, a `cuid` id on Postgres and a foreign key into a\n * composite key are all configurations that produce a broken database in\n * silence, so they are refused where every path passes.\n */\nimport type {\n CollectionConfig,\n NumberProperty,\n Property,\n StringProperty\n} from \"@rebasepro/types\";\nimport { toSnakeCase } from \"@rebasepro/utils\";\n\n/**\n * Resolve the SQL column name for a property.\n *\n * Uses the explicit `columnName` when set (e.g. from introspection), falling\n * back to `toSnakeCase(propName)` for manually-authored collections.\n */\nexport const resolveColumnName = (propName: string, prop?: Property | null): string => {\n if (prop && \"columnName\" in prop && typeof prop.columnName === \"string\") {\n return prop.columnName;\n }\n return toSnakeCase(propName);\n};\n\n/** Every property that carries a truthy `isId`, in declaration order. */\nconst idPropertyEntries = (collection: CollectionConfig): [string, Property][] =>\n Object.entries(collection.properties ?? {}).filter(\n ([, prop]) => prop && typeof prop === \"object\" && \"isId\" in prop && Boolean((prop as { isId?: unknown }).isId)\n ) as [string, Property][];\n\n/**\n * The properties marked `isId`, in declaration order.\n *\n * Several of them are one composite primary key: the table carries a single\n * `PRIMARY KEY (a, b)` constraint over their columns, and a row's address lists\n * the parts in this same order (`buildCompositeId`), which is the order the\n * admin reads them in too.\n */\nexport const primaryKeyPropertyNames = (collection: CollectionConfig): string[] =>\n idPropertyEntries(collection).map(([name]) => name);\n\n/**\n * A collection's primary key, by both of its names.\n *\n * `name` is the property key — what the generated Drizzle table is keyed by,\n * so it is what a relation's `references` names. `column` is the SQL\n * identifier, so it is what a foreign key's `REFERENCES` names. They differ\n * whenever the key is camelCase or carries a `columnName` (`authorId` is\n * `author_id`, and every table `rebase schema introspect` adopts is spelled\n * that way), and a constraint written with the key named a column that does\n * not exist: `db push` could not build its desired state, and boot failed the\n * constraint on every start.\n */\nexport interface PrimaryKey {\n /** Property key: the Drizzle object key and the JSON field. */\n name: string;\n /** SQL column. */\n column: string;\n /** The declaring property; absent for the implicit `id TEXT` of a collection that declares none. */\n prop?: Property;\n}\n\n/**\n * The one column a link into this collection points at.\n *\n * A foreign key column references a single column, and Postgres only accepts\n * that column when it is unique on its own — `REFERENCES t (a)` against a\n * composite key `(a, b)` fails with \"there is no unique constraint matching\n * given keys for referenced table\". So a link into a collection whose key\n * spans several columns has no column to point at, and it is refused here,\n * naming the link, rather than emitted as a constraint `db push` or boot would\n * then fail to create.\n *\n * @param via the link that needs the column, for the refusal: `relation \"posts.author\"`.\n */\nexport const getPrimaryKeyProp = (collection: CollectionConfig, via: string): PrimaryKey => {\n const ids = idPropertyEntries(collection);\n if (ids.length > 1) {\n throw new Error(\n `${via} points at collection \"${collection.slug ?? collection.name ?? \"(unnamed)\"}\", whose primary ` +\n `key spans ${ids.length} columns (${ids.map(([name]) => `\"${name}\"`).join(\", \")}). A foreign key is ` +\n \"one column and cannot reference a composite key. Point the link at a collection with a \" +\n \"single-column key, or reach these rows with a `via` relation, whose `joinPath` compares every \" +\n \"key column.\"\n );\n }\n const idPropEntry = ids[0];\n if (idPropEntry) {\n const [name, prop] = idPropEntry;\n return { name, column: resolveColumnName(name, prop), prop };\n }\n // Fallback: a collection that declares no `isId` gets an implicit `id`.\n const idProp = collection.properties?.[\"id\"] as Property | undefined;\n return { name: \"id\", column: resolveColumnName(\"id\", idProp), prop: idProp };\n};\n\n/**\n * The primary key's property key. What a Drizzle relation's `references`\n * names. Refused on the same terms as {@link getPrimaryKeyProp}.\n */\nexport const getPrimaryKeyName = (collection: CollectionConfig, via: string): string =>\n getPrimaryKeyProp(collection, via).name;\n\n/**\n * The primary key's column. What a foreign key's `REFERENCES` names. Refused\n * on the same terms as {@link getPrimaryKeyProp}.\n */\nexport const getPrimaryKeyColumn = (collection: CollectionConfig, via: string): string =>\n getPrimaryKeyProp(collection, via).column;\n\nexport const isIdProperty = (propName: string, prop: Property, collection: CollectionConfig): boolean => {\n if (\"isId\" in prop && Boolean(prop.isId)) return true;\n\n // Only fall back to \"id\" when NO property is explicitly marked.\n return idPropertyEntries(collection).length === 0 && propName === \"id\";\n};\n\n/**\n * The labels an `enum` declares, in order, whichever form it was written in.\n *\n * `EnumValues` is `EnumValueConfig[] | Record<id, label>` and the docs recommend\n * the record. Boot-ensure read only the array form — `(p.enum as unknown[]).map`\n * — so a record-form enum threw `p.enum.map is not a function` on a managed\n * tenant, at boot, with no developer in the loop. The other two emitters each\n * carried their own copy of the both-forms walk.\n *\n * An empty list throws rather than returning `[]`. `CREATE TYPE … AS ENUM ()`\n * is not valid SQL, so every emitter \"handled\" it by skipping the type and then\n * typing the column with it anyway: Drizzle referenced an enum variable it never\n * declared (the generated file does not compile), the DDL named a type nothing\n * creates, and boot-ensure's `ADD COLUMN` failed — and add-column is not a\n * survivable action, so the boot died. There is no useful column at the end of\n * any of those paths.\n */\nexport const enumLabelsOf = (propName: string, prop: Property, collection: CollectionConfig): string[] => {\n const values = (prop as { enum?: unknown }).enum;\n if (values === undefined || values === null) return [];\n\n const labels = Array.isArray(values)\n ? values.map((entry: unknown) =>\n String(entry !== null && typeof entry === \"object\" && \"id\" in (entry as Record<string, unknown>)\n ? (entry as Record<string, unknown>).id\n : entry))\n : Object.keys(values as Record<string, unknown>).map(String);\n\n const usable = labels.filter(label => label.length > 0);\n if (usable.length === 0) {\n throw new Error(\n `Property \"${propName}\" of collection \"${collection.slug ?? collection.name ?? \"(unnamed)\"}\" ` +\n \"declares an empty `enum`. A Postgres enum type needs at least one label — \" +\n \"`CREATE TYPE … AS ENUM ()` is not valid SQL — so the column would reference a type \" +\n \"nothing creates. List the values, or drop the `enum` for a plain column.\"\n );\n }\n return usable;\n};\n\n/**\n * Does this property compile to a Postgres enum *type*?\n *\n * Strings only. A `number` property with an `enum` used to create a type on all\n * three paths and then declare the column `NUMERIC`/`INTEGER`, so the type was\n * orphaned the moment it was created and the values it listed were enforced by\n * nothing. The dropdown in the panel is driven by the config, not the column, so\n * nothing is lost by not creating it — and a type no column uses is one more\n * object `db push` has to plan a DROP for.\n */\nexport const declaresEnumType = (prop: Property): boolean =>\n prop.type === \"string\" && Boolean((prop as StringProperty).enum);\n\n/**\n * The `DEFAULT` an id column carries, as the thing itself rather than a\n * rendered string — each emitter spells it differently.\n *\n * The three of them disagreed on all four strategies. The DDL generator wrote\n * `DEFAULT ${prop.isId}` verbatim, so the documented `` isId: \"sql`gen_id()`\" ``\n * reached Postgres with the template wrapper still on it — a syntax error in\n * `schema.sql`. Boot-ensure gave a default to `uuid` and to nothing else, so on\n * a managed tenant every other strategy produced a column with no default at\n * all, and the first insert that trusted the config failed on a NULL primary\n * key. Only the Drizzle generator stripped the wrapper.\n */\nexport type IdColumnDefault =\n /** `gen_random_uuid()` / `.defaultRandom()`. */\n | { kind: \"uuid\" }\n /** `GENERATED BY DEFAULT AS IDENTITY` / `.generatedByDefaultAsIdentity()` — part of the type, not a DEFAULT. */\n | { kind: \"identity\" }\n /** A raw SQL expression, wrapper already stripped. */\n | { kind: \"sql\"; expression: string };\n\n/**\n * Refuses `isId: \"cuid\"`.\n *\n * `DEFAULT cuid()` has been emitted since the option existed and no `cuid()`\n * function has ever been created — not by the generators, not by boot, not by\n * any migration. So the column has never had a working default on Postgres: the\n * first insert that relied on it failed with `function cuid() does not exist`.\n * There is nothing to keep compatible.\n *\n * It is refused rather than implemented. Shipping a real `rebase.cuid()` means a\n * new SQL function in the same carved-out class as the search helpers — excluded\n * from Atlas by pattern, applied by boot, re-applied by push, checked by the\n * doctor, and versioned across the fleet forever — to reproduce a monotonic id\n * that `uuid` already provides and that a project wanting cuid's exact shape can\n * write itself in one line of SQL.\n */\nexport const idColumnDefault = (propName: string, prop: Property, collection: CollectionConfig): IdColumnDefault | undefined => {\n if (!(\"isId\" in prop)) return undefined;\n const isId = (prop as { isId?: unknown }).isId;\n // `true` and `\"manual\"` both mean \"the application supplies it\".\n if (isId === undefined || isId === null || isId === true || isId === \"manual\" || isId === false) return undefined;\n if (typeof isId !== \"string\") return undefined;\n\n if (prop.type === \"string\" && (isId as StringProperty[\"isId\"]) === \"uuid\") return { kind: \"uuid\" };\n if (prop.type === \"number\" && (isId as NumberProperty[\"isId\"]) === \"increment\") return { kind: \"identity\" };\n\n if (isId === \"cuid\") {\n throw new Error(\n `Property \"${propName}\" of collection \"${collection.slug ?? collection.name ?? \"(unnamed)\"}\" ` +\n \"uses `isId: \\\"cuid\\\"`, which Postgres cannot honour: the generated default calls a `cuid()` \" +\n \"function that Rebase has never created, so every insert relying on it fails. \" +\n \"Use `isId: \\\"uuid\\\"`, or give the strategy as SQL — ``isId: \\\"sql`my_id()`\\\"`` — and create that \" +\n \"function in a migration.\"\n );\n }\n\n // Any other string is a raw SQL default. Written either bare\n // (`gen_random_uuid()::text`) or in the documented template form\n // (``sql`gen_random_uuid()::text` ``); the wrapper is markup, not SQL.\n return { kind: \"sql\", expression: sqlExpressionText(isId) };\n};\n\n/**\n * The SQL inside a template-form expression (``sql`now()` `` → `now()`); a\n * bare expression is returned as it is.\n */\nexport const sqlExpressionText = (value: string): string =>\n value.startsWith(\"sql`\") && value.endsWith(\"`\") && value.length >= 5\n ? value.substring(4, value.length - 1)\n : value;\n"],"mappings":"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"}
1
+ {"version":3,"file":"column-plan-helpers-IG6uFb1s.js","names":[],"sources":["../src/schema/column-plan-helpers.ts"],"sourcesContent":["/**\n * The questions about a property that the schema planner asks first.\n *\n * What is the primary key, what is this column called, what does an id default\n * to, what labels does this enum have. `schema/plan/plan-schema.ts` is the only\n * caller — it is the only reader of a `Property` — and these live beside it\n * rather than inside it because they are also where the *refusals* are, and a\n * refusal wants to be reachable and testable on its own.\n *\n * They were extracted while there were still three emitters, each carrying its\n * own verbatim copy of all four rules until one of them was edited: the audit\n * that produced this module counted twelve open disagreements and found that a\n * third of the commits touching those files were re-synchronisations. The\n * emitters are renderers now and there is nothing left to keep in step, but the\n * questions are still questions about a property rather than about a plan, so\n * this is where they belong.\n *\n * Everything here is pure and synchronous, and it throws rather than guesses:\n * an enum with no values, a `cuid` id on Postgres and a foreign key into a\n * composite key are all configurations that produce a broken database in\n * silence, so they are refused where every path passes.\n */\nimport type {\n CollectionConfig,\n NumberProperty,\n Property,\n StringProperty\n} from \"@rebasepro/types\";\nimport { toSnakeCase } from \"@rebasepro/utils\";\n\n/**\n * Resolve the SQL column name for a property.\n *\n * Uses the explicit `columnName` when set (e.g. from introspection), falling\n * back to `toSnakeCase(propName)` for manually-authored collections.\n */\nexport const resolveColumnName = (propName: string, prop?: Property | null): string => {\n if (prop && \"columnName\" in prop && typeof prop.columnName === \"string\") {\n return prop.columnName;\n }\n return toSnakeCase(propName);\n};\n\n/** Every property that carries a truthy `isId`, in declaration order. */\nconst idPropertyEntries = (collection: CollectionConfig): [string, Property][] =>\n Object.entries(collection.properties ?? {}).filter(\n ([, prop]) => prop && typeof prop === \"object\" && \"isId\" in prop && Boolean((prop as { isId?: unknown }).isId)\n ) as [string, Property][];\n\n/**\n * The properties marked `isId`, in declaration order.\n *\n * Several of them are one composite primary key: the table carries a single\n * `PRIMARY KEY (a, b)` constraint over their columns, and a row's address lists\n * the parts in this same order (`buildCompositeId`), which is the order the\n * admin reads them in too.\n */\nexport const primaryKeyPropertyNames = (collection: CollectionConfig): string[] =>\n idPropertyEntries(collection).map(([name]) => name);\n\n/**\n * A collection's primary key, by both of its names.\n *\n * `name` is the property key — what the generated Drizzle table is keyed by,\n * so it is what a relation's `references` names. `column` is the SQL\n * identifier, so it is what a foreign key's `REFERENCES` names. They differ\n * whenever the key is camelCase or carries a `columnName` (`authorId` is\n * `author_id`, and every table `rebase schema introspect` adopts is spelled\n * that way), and a constraint written with the key named a column that does\n * not exist: `db push` could not build its desired state, and boot failed the\n * constraint on every start.\n */\nexport interface PrimaryKey {\n /** Property key: the Drizzle object key and the JSON field. */\n name: string;\n /** SQL column. */\n column: string;\n /** The declaring property; absent for the implicit `id TEXT` of a collection that declares none. */\n prop?: Property;\n}\n\n/**\n * The one column a link into this collection points at.\n *\n * A foreign key column references a single column, and Postgres only accepts\n * that column when it is unique on its own — `REFERENCES t (a)` against a\n * composite key `(a, b)` fails with \"there is no unique constraint matching\n * given keys for referenced table\". So a link into a collection whose key\n * spans several columns has no column to point at, and it is refused here,\n * naming the link, rather than emitted as a constraint `db push` or boot would\n * then fail to create.\n *\n * @param via the link that needs the column, for the refusal: `relation \"posts.author\"`.\n */\nexport const getPrimaryKeyProp = (collection: CollectionConfig, via: string): PrimaryKey => {\n const ids = idPropertyEntries(collection);\n if (ids.length > 1) {\n throw new Error(\n `${via} points at collection \"${collection.slug ?? collection.name ?? \"(unnamed)\"}\", whose primary ` +\n `key spans ${ids.length} columns (${ids.map(([name]) => `\"${name}\"`).join(\", \")}). A foreign key is ` +\n \"one column and cannot reference a composite key. Point the link at a collection with a \" +\n \"single-column key, or reach these rows with a `via` relation, whose `joinPath` compares every \" +\n \"key column.\"\n );\n }\n const idPropEntry = ids[0];\n if (idPropEntry) {\n const [name, prop] = idPropEntry;\n return { name, column: resolveColumnName(name, prop), prop };\n }\n // Fallback: a collection that declares no `isId` gets an implicit `id`.\n const idProp = collection.properties?.[\"id\"] as Property | undefined;\n return { name: \"id\", column: resolveColumnName(\"id\", idProp), prop: idProp };\n};\n\n/**\n * The primary key's property key. What a Drizzle relation's `references`\n * names. Refused on the same terms as {@link getPrimaryKeyProp}.\n */\nexport const getPrimaryKeyName = (collection: CollectionConfig, via: string): string =>\n getPrimaryKeyProp(collection, via).name;\n\n/**\n * The primary key's column. What a foreign key's `REFERENCES` names. Refused\n * on the same terms as {@link getPrimaryKeyProp}.\n */\nexport const getPrimaryKeyColumn = (collection: CollectionConfig, via: string): string =>\n getPrimaryKeyProp(collection, via).column;\n\nexport const isIdProperty = (propName: string, prop: Property, collection: CollectionConfig): boolean => {\n if (\"isId\" in prop && Boolean(prop.isId)) return true;\n\n // Only fall back to \"id\" when NO property is explicitly marked.\n return idPropertyEntries(collection).length === 0 && propName === \"id\";\n};\n\n/**\n * The labels an `enum` declares, in order, whichever form it was written in.\n *\n * `EnumValues` is `EnumValueConfig[] | Record<id, label>` and the docs recommend\n * the record. Boot-ensure read only the array form — `(p.enum as unknown[]).map`\n * — so a record-form enum threw `p.enum.map is not a function` on a managed\n * tenant, at boot, with no developer in the loop. The other two emitters each\n * carried their own copy of the both-forms walk.\n *\n * An empty list throws rather than returning `[]`. `CREATE TYPE … AS ENUM ()`\n * is not valid SQL, so every emitter \"handled\" it by skipping the type and then\n * typing the column with it anyway: Drizzle referenced an enum variable it never\n * declared (the generated file does not compile), the DDL named a type nothing\n * creates, and boot-ensure's `ADD COLUMN` failed — and add-column is not a\n * survivable action, so the boot died. There is no useful column at the end of\n * any of those paths.\n */\nexport const enumLabelsOf = (propName: string, prop: Property, collection: CollectionConfig): string[] => {\n const values = (prop as { enum?: unknown }).enum;\n if (values === undefined || values === null) return [];\n\n const labels = Array.isArray(values)\n ? values.map((entry: unknown) =>\n String(entry !== null && typeof entry === \"object\" && \"id\" in (entry as Record<string, unknown>)\n ? (entry as Record<string, unknown>).id\n : entry))\n : Object.keys(values as Record<string, unknown>).map(String);\n\n const usable = labels.filter(label => label.length > 0);\n if (usable.length === 0) {\n throw new Error(\n `Property \"${propName}\" of collection \"${collection.slug ?? collection.name ?? \"(unnamed)\"}\" ` +\n \"declares an empty `enum`. A Postgres enum type needs at least one label — \" +\n \"`CREATE TYPE … AS ENUM ()` is not valid SQL — so the column would reference a type \" +\n \"nothing creates. List the values, or drop the `enum` for a plain column.\"\n );\n }\n return usable;\n};\n\n/**\n * Does this property compile to a Postgres enum *type*?\n *\n * Strings only. A `number` property with an `enum` used to create a type on all\n * three paths and then declare the column `NUMERIC`/`INTEGER`, so the type was\n * orphaned the moment it was created and the values it listed were enforced by\n * nothing. The dropdown in the panel is driven by the config, not the column, so\n * nothing is lost by not creating it — and a type no column uses is one more\n * object `db push` has to plan a DROP for.\n */\nexport const declaresEnumType = (prop: Property): boolean =>\n prop.type === \"string\" && Boolean((prop as StringProperty).enum);\n\n/**\n * The `DEFAULT` an id column carries, as the thing itself rather than a\n * rendered string — each emitter spells it differently.\n *\n * The three of them disagreed on all four strategies. The DDL generator wrote\n * `DEFAULT ${prop.isId}` verbatim, so the documented `` isId: \"sql`gen_id()`\" ``\n * reached Postgres with the template wrapper still on it — a syntax error in\n * `schema.sql`. Boot-ensure gave a default to `uuid` and to nothing else, so on\n * a managed tenant every other strategy produced a column with no default at\n * all, and the first insert that trusted the config failed on a NULL primary\n * key. Only the Drizzle generator stripped the wrapper.\n */\nexport type IdColumnDefault =\n /** `gen_random_uuid()` / `.defaultRandom()`. */\n | { kind: \"uuid\" }\n /** `GENERATED BY DEFAULT AS IDENTITY` / `.generatedByDefaultAsIdentity()` — part of the type, not a DEFAULT. */\n | { kind: \"identity\" }\n /** A raw SQL expression, wrapper already stripped. */\n | { kind: \"sql\"; expression: string };\n\n/**\n * Refuses `isId: \"cuid\"`.\n *\n * `DEFAULT cuid()` has been emitted since the option existed and no `cuid()`\n * function has ever been created — not by the generators, not by boot, not by\n * any migration. So the column has never had a working default on Postgres: the\n * first insert that relied on it failed with `function cuid() does not exist`.\n * There is nothing to keep compatible.\n *\n * It is refused rather than implemented. Shipping a real `rebase.cuid()` means a\n * new SQL function in the same carved-out class as the search helpers — excluded\n * from Atlas by pattern, applied by boot, re-applied by push, checked by the\n * doctor, and versioned across the fleet forever — to reproduce a monotonic id\n * that `uuid` already provides and that a project wanting cuid's exact shape can\n * write itself in one line of SQL.\n */\nexport const idColumnDefault = (propName: string, prop: Property, collection: CollectionConfig): IdColumnDefault | undefined => {\n if (!(\"isId\" in prop)) return undefined;\n const isId = (prop as { isId?: unknown }).isId;\n // `true` and `\"manual\"` both mean \"the application supplies it\".\n if (isId === undefined || isId === null || isId === true || isId === \"manual\" || isId === false) return undefined;\n if (typeof isId !== \"string\") return undefined;\n\n if (prop.type === \"string\" && (isId as StringProperty[\"isId\"]) === \"uuid\") return { kind: \"uuid\" };\n if (prop.type === \"number\" && (isId as NumberProperty[\"isId\"]) === \"increment\") return { kind: \"identity\" };\n\n if (isId === \"cuid\") {\n throw new Error(\n `Property \"${propName}\" of collection \"${collection.slug ?? collection.name ?? \"(unnamed)\"}\" ` +\n \"uses `isId: \\\"cuid\\\"`, which Postgres cannot honour: the generated default calls a `cuid()` \" +\n \"function that Rebase has never created, so every insert relying on it fails. \" +\n \"Use `isId: \\\"uuid\\\"`, or give the strategy as SQL — ``isId: \\\"sql`my_id()`\\\"`` — and create that \" +\n \"function in a migration.\"\n );\n }\n\n // Any other string is a raw SQL default. Written either bare\n // (`gen_random_uuid()::text`) or in the documented template form\n // (``sql`gen_random_uuid()::text` ``); the wrapper is markup, not SQL.\n return { kind: \"sql\", expression: sqlExpressionText(isId) };\n};\n\n/**\n * The SQL inside a template-form expression (``sql`now()` `` → `now()`); a\n * bare expression is returned as it is.\n */\nexport const sqlExpressionText = (value: string): string =>\n value.startsWith(\"sql`\") && value.endsWith(\"`\") && value.length >= 5\n ? value.substring(4, value.length - 1)\n : value;\n"],"mappings":"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"}
@@ -1,9 +1,60 @@
1
1
  import { createRequire as __createRequire } from "module";
2
2
  __createRequire(import.meta.url);
3
- import { n as __exportAll } from "./rolldown-runtime-V7rFbMRe.js";
3
+ import { i as __exportAll } from "./cli-prompt-DXG0yk-h.js";
4
4
  import { Pool } from "pg";
5
5
  import { drizzle } from "drizzle-orm/node-postgres";
6
6
  import { logger } from "@rebasepro/server";
7
+ //#region src/pool-disconnects.ts
8
+ /**
9
+ * What a pool says when the server ends its idle connections.
10
+ *
11
+ * `pg_terminate_backend` ends every connection to a database at once, and an
12
+ * idle pool client hears it as an `error` event: one per connection, from
13
+ * every pool the process holds. `rebase db branch create --force` does exactly
14
+ * that to the database it copies, so fifteen forced creates beside a running
15
+ * `rebase dev` printed 220 `❌ [ERROR] [pg-pool] Unexpected pool error
16
+ * {"detail":"terminating connection due to administrator command"}` lines. It
17
+ * read as an outage, and nothing was wrong: the pool opens new connections on
18
+ * the next query. A database restart looks the same.
19
+ */
20
+ /** SQLSTATEs of a connection the server ended: admin_shutdown, crash_shutdown, cannot_connect_now. */
21
+ var SERVER_ENDED_CODES = /* @__PURE__ */ new Set([
22
+ "57P01",
23
+ "57P02",
24
+ "57P03"
25
+ ]);
26
+ /** Did the server end this connection, rather than something go wrong with it? */
27
+ function serverEndedConnection(err) {
28
+ const code = "code" in err ? err.code : void 0;
29
+ return typeof code === "string" && SERVER_ENDED_CODES.has(code) || /terminating connection due to administrator command/i.test(err.message);
30
+ }
31
+ /** How long one line stands for every server-ended connection after it, across all pools. */
32
+ var DISCONNECT_BURST_MS = 1e4;
33
+ /** When the current burst's line stops covering further disconnects. */
34
+ var burstUntil = 0;
35
+ /**
36
+ * Say a pool's `error` event: a connection the server ended is one WARN line
37
+ * per burst for the whole process, and the rest of the burst is at debug;
38
+ * anything else is the ERROR it always was.
39
+ *
40
+ * The window is fixed from the first disconnect, not extended by each, so a
41
+ * second burst ten seconds later is said again.
42
+ */
43
+ function reportPoolError(label, err, log = logger, now = Date.now) {
44
+ if (!serverEndedConnection(err)) {
45
+ log.error(`[${label}] Unexpected pool error`, { detail: err.message });
46
+ return;
47
+ }
48
+ const at = now();
49
+ if (at < burstUntil) {
50
+ log.debug(`[${label}] The database ended another idle connection`, { detail: err.message });
51
+ return;
52
+ }
53
+ burstUntil = at + DISCONNECT_BURST_MS;
54
+ const code = "code" in err && typeof err.code === "string" ? `, ${err.code}` : "";
55
+ log.warn(`[${label}] The database ended this server's idle connections (${err.message}${code}). New ones open on the next query. A database restart does this, and so does \`rebase db branch create --force\`, which closes the database it copies.`);
56
+ }
57
+ //#endregion
7
58
  //#region src/connection.ts
8
59
  var connection_exports = /* @__PURE__ */ __exportAll({
9
60
  cappedPoolMax: () => cappedPoolMax,
@@ -180,7 +231,7 @@ function createPostgresDatabaseConnection(connectionString, schema, poolConfig,
180
231
  keepAliveInitialDelayMillis: 0
181
232
  });
182
233
  pool.on("error", (err) => {
183
- logger.error("[pg-pool] Unexpected pool error", { detail: err.message });
234
+ reportPoolError("pg-pool", err);
184
235
  if (err.message.includes("ETIMEDOUT")) logger.warn("[pg-pool] Connection timeout detected — pool will auto-retry");
185
236
  });
186
237
  guardPoolAgainstDirtyRelease(pool, "pg-pool");
@@ -214,9 +265,7 @@ function createDirectDatabaseConnection(connectionString, schema, poolConfig) {
214
265
  keepAlive: opts.keepAlive,
215
266
  keepAliveInitialDelayMillis: 0
216
267
  });
217
- pool.on("error", (err) => {
218
- logger.error("[pg-direct-pool] Unexpected pool error", { detail: err.message });
219
- });
268
+ pool.on("error", (err) => reportPoolError("pg-direct-pool", err));
220
269
  guardPoolAgainstDirtyRelease(pool, "pg-direct-pool");
221
270
  return {
222
271
  db: schema ? drizzle(pool, { schema }) : drizzle(pool),
@@ -245,9 +294,7 @@ function createReadReplicaConnection(connectionString, schema, poolConfig) {
245
294
  keepAlive: opts.keepAlive,
246
295
  keepAliveInitialDelayMillis: 0
247
296
  });
248
- pool.on("error", (err) => {
249
- logger.error("[pg-replica-pool] Unexpected pool error", { detail: err.message });
250
- });
297
+ pool.on("error", (err) => reportPoolError("pg-replica-pool", err));
251
298
  guardPoolAgainstDirtyRelease(pool, "pg-replica-pool");
252
299
  return {
253
300
  db: schema ? drizzle(pool, { schema }) : drizzle(pool),
@@ -256,6 +303,6 @@ function createReadReplicaConnection(connectionString, schema, poolConfig) {
256
303
  };
257
304
  }
258
305
  //#endregion
259
- export { createReadReplicaConnection as a, pinSearchPath as c, createPostgresDatabaseConnection as i, poolMaxCeiling as l, connection_exports as n, defaultPoolTimeouts as o, createDirectDatabaseConnection as r, guardPoolAgainstDirtyRelease as s, cappedPoolMax as t };
306
+ export { createReadReplicaConnection as a, pinSearchPath as c, createPostgresDatabaseConnection as i, poolMaxCeiling as l, connection_exports as n, defaultPoolTimeouts as o, createDirectDatabaseConnection as r, guardPoolAgainstDirtyRelease as s, cappedPoolMax as t, reportPoolError as u };
260
307
 
261
- //# sourceMappingURL=connection-C938nR3E.js.map
308
+ //# sourceMappingURL=connection-czvI7TZL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection-czvI7TZL.js","names":[],"sources":["../src/pool-disconnects.ts","../src/connection.ts"],"sourcesContent":["/**\n * What a pool says when the server ends its idle connections.\n *\n * `pg_terminate_backend` ends every connection to a database at once, and an\n * idle pool client hears it as an `error` event: one per connection, from\n * every pool the process holds. `rebase db branch create --force` does exactly\n * that to the database it copies, so fifteen forced creates beside a running\n * `rebase dev` printed 220 `❌ [ERROR] [pg-pool] Unexpected pool error\n * {\"detail\":\"terminating connection due to administrator command\"}` lines. It\n * read as an outage, and nothing was wrong: the pool opens new connections on\n * the next query. A database restart looks the same.\n */\nimport { logger } from \"@rebasepro/server\";\n\n/** SQLSTATEs of a connection the server ended: admin_shutdown, crash_shutdown, cannot_connect_now. */\nconst SERVER_ENDED_CODES = new Set([\"57P01\", \"57P02\", \"57P03\"]);\n\n/** Did the server end this connection, rather than something go wrong with it? */\nexport function serverEndedConnection(err: Error): boolean {\n const code = \"code\" in err ? err.code : undefined;\n return (typeof code === \"string\" && SERVER_ENDED_CODES.has(code))\n || /terminating connection due to administrator command/i.test(err.message);\n}\n\n/** How long one line stands for every server-ended connection after it, across all pools. */\nexport const DISCONNECT_BURST_MS = 10_000;\n\n/** When the current burst's line stops covering further disconnects. */\nlet burstUntil = 0;\n\n/** The logger methods a pool's error listener writes through. */\nexport type PoolErrorLog = Pick<typeof logger, \"warn\" | \"error\" | \"debug\">;\n\n/**\n * Say a pool's `error` event: a connection the server ended is one WARN line\n * per burst for the whole process, and the rest of the burst is at debug;\n * anything else is the ERROR it always was.\n *\n * The window is fixed from the first disconnect, not extended by each, so a\n * second burst ten seconds later is said again.\n */\nexport function reportPoolError(label: string, err: Error, log: PoolErrorLog = logger, now: () => number = Date.now): void {\n if (!serverEndedConnection(err)) {\n log.error(`[${label}] Unexpected pool error`, { detail: err.message });\n return;\n }\n const at = now();\n if (at < burstUntil) {\n log.debug(`[${label}] The database ended another idle connection`, { detail: err.message });\n return;\n }\n burstUntil = at + DISCONNECT_BURST_MS;\n const code = \"code\" in err && typeof err.code === \"string\" ? `, ${err.code}` : \"\";\n log.warn(\n `[${label}] The database ended this server's idle connections (${err.message}${code}). ` +\n \"New ones open on the next query. A database restart does this, and so does \" +\n \"`rebase db branch create --force`, which closes the database it copies.\"\n );\n}\n","import { Pool, PoolConfig } from \"pg\";\nimport { drizzle } from \"drizzle-orm/node-postgres\";\nimport { logger } from \"@rebasepro/server\";\n\nimport { reportPoolError } from \"./pool-disconnects\";\n\n/**\n * Configuration for the Postgres connection pool.\n *\n * Sensible defaults are provided for production Cloud Run / single-instance\n * deployments. Override via environment variables or explicit config.\n */\nexport interface PostgresPoolConfig {\n /** Maximum number of connections in the pool (default: 20) */\n max?: number;\n /** Close idle connections after this many ms (default: 30 000) */\n idleTimeoutMillis?: number;\n /** Abort connection attempts after this many ms (default: 10 000) */\n connectionTimeoutMillis?: number;\n /** Per-query timeout in ms (default: 30 000) */\n queryTimeout?: number;\n /** Per-statement timeout in ms (default: 30 000) */\n statementTimeout?: number;\n /** Enable TCP keep-alive (default: true) */\n keepAlive?: boolean;\n /**\n * `search_path` pinned on every connection (default: `\"public\"`).\n *\n * Pass `false` to send no `search_path` at all and inherit whatever the\n * server/role defaults to. See {@link pinSearchPath} for why the default\n * is not \"inherit\".\n */\n searchPath?: string | false;\n}\n\n/**\n * Ceiling on pool size, from the environment.\n *\n * Exists for the managed development database. PGlite is a single session\n * behind a multiplexing socket server, and two pooled clients holding\n * overlapping transactions deadlock there — which a request-per-transaction\n * server produces the moment two requests overlap. One connection turns that\n * deadlock into ordinary queueing.\n *\n * A ceiling rather than a value, so it constrains every pool in the process\n * without any of them having to know why. Ignored when unset or unparseable,\n * because a malformed limit must not silently serialize a production server.\n */\nexport function poolMaxCeiling(env: NodeJS.ProcessEnv = process.env): number | null {\n const raw = env.REBASE_DB_POOL_MAX;\n if (typeof raw !== \"string\") return null;\n\n // Plain decimal digits only. `Number` would also accept `1e3`, `0x10` and\n // ` 4 ` — none of which anyone writes on purpose in a config value, and all\n // of which would mean this silently did something other than it appears to.\n if (!/^[0-9]+$/.test(raw.trim())) return null;\n\n const parsed = Number(raw.trim());\n\n return parsed >= 1 ? parsed : null;\n}\n\n/** Apply {@link poolMaxCeiling} to a requested pool size. */\nexport function cappedPoolMax(requested: number, env: NodeJS.ProcessEnv = process.env): number {\n const ceiling = poolMaxCeiling(env);\n\n return ceiling === null ? requested : Math.min(requested, ceiling);\n}\n\nconst DEFAULT_POOL: Required<PostgresPoolConfig> = {\n max: 20,\n searchPath: \"public\",\n idleTimeoutMillis: 30_000,\n connectionTimeoutMillis: 10_000,\n // The client-side read timeout MUST be comfortably above the server-side\n // statement_timeout. When the client timer fires first, node-postgres\n // abandons the in-flight statement but keeps the connection — inside a\n // transaction that leaves the tx open (and any pending ROLLBACK is\n // spliced out of the client queue before it ever reaches the wire), so\n // the pooled connection is returned still in-transaction with its RLS\n // GUCs set. The server abort (SQLSTATE 57014) is the clean path; the\n // client timeout is only a backstop for a dead network.\n queryTimeout: 60_000,\n statementTimeout: 30_000,\n keepAlive: true\n};\n\n/**\n * The time limits every pool to this server shares: how long to wait for a\n * connection (then 503), the server-side statement timeout, the client-side\n * read backstop above it, and TCP keep-alive. Exported for the per-database\n * pools (`DatabasePoolManager`), which had none of them — pg's default\n * `connectionTimeoutMillis` is 0, wait forever.\n */\nexport function defaultPoolTimeouts(): Pick<PoolConfig,\n \"connectionTimeoutMillis\" | \"query_timeout\" | \"statement_timeout\" | \"keepAlive\" | \"keepAliveInitialDelayMillis\"> {\n return {\n connectionTimeoutMillis: DEFAULT_POOL.connectionTimeoutMillis,\n query_timeout: DEFAULT_POOL.queryTimeout,\n statement_timeout: DEFAULT_POOL.statementTimeout,\n keepAlive: DEFAULT_POOL.keepAlive,\n keepAliveInitialDelayMillis: 0\n };\n}\n\n/** ReadyForQuery status byte: `I` idle, `T` in transaction, `E` failed transaction. */\nconst TX_IDLE = \"I\";\n\n/**\n * Pin `search_path` into a connection string, so unqualified SQL resolves to a\n * schema this framework chose rather than to one Postgres inferred.\n *\n * Postgres defaults `search_path` to `\"$user\", public`: the *first* candidate\n * is a schema named after the connecting role. Rebase creates a schema called\n * `rebase` (auth, history, api keys), and every template, compose file and\n * deployment doc names the database role `rebase` too — so `$user` resolves to\n * a schema that exists, and every unqualified statement lands there instead of\n * in `public`. The generated Drizzle schema emits bare `pgTable(\"posts\", …)`\n * for any collection without an explicit `schema`, which makes the *runtime's*\n * own reads and writes unqualified; a developer's raw `rebase.sql(...)`, the\n * Studio SQL editor and any hand-written migration are unqualified too. The\n * result is collection tables created in, and served from, `rebase`.\n *\n * Drizzle cannot express the fix on its side: `pgSchema(\"public\")` throws by\n * design (\"just use pgTable() instead\"), so there is no way to emit a\n * public-qualified table from the generator. The pin has to live on the\n * connection.\n *\n * Precedence is deliberate and verified against node-postgres: `options` in\n * the connection string wins over the `options` field passed to `Pool`, so\n * rewriting the URL — rather than setting the field — is what makes this\n * authoritative. Two escape hatches survive it:\n *\n * - an `options` that already mentions `search_path` is left untouched, so a\n * deployment that deliberately pins something else keeps it;\n * - `searchPath: false` (or an unparseable, non-URL connection string) sends\n * nothing and inherits the server default.\n *\n * Anything else in `options` (a `statement_timeout`, say) is preserved and the\n * `search_path` flag is appended to it.\n */\nexport function pinSearchPath(connectionString: string, searchPath: string | false = \"public\"): string {\n if (searchPath === false) return connectionString;\n\n let url: URL;\n try {\n url = new URL(connectionString);\n } catch {\n // Key/value DSNs and anything else we cannot parse are returned as\n // given: a connection that works unpinned beats one we corrupted.\n return connectionString;\n }\n if (url.protocol !== \"postgres:\" && url.protocol !== \"postgresql:\") return connectionString;\n\n const existing = url.searchParams.get(\"options\");\n if (existing && /(^|\\s)-c\\s*search_path\\s*=/.test(existing)) return connectionString;\n\n const flag = `-c search_path=${searchPath}`;\n url.searchParams.set(\"options\", existing ? `${existing} ${flag}` : flag);\n // Re-serialize by hand. `URLSearchParams` writes a space as `+`, which\n // node-postgres happens to decode but libpq does not — and this same string\n // is handed to `pg_dump`/`psql` for backups. Percent-encoding is the form\n // both agree on, and is what the scaffolded `.env` already ships.\n url.search = Array.from(url.searchParams.entries())\n .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)\n .join(\"&\");\n return url.toString();\n}\n\n/**\n * Destroy pool clients that are released while still inside a transaction.\n *\n * pg-pool returns a client to the idle list whenever `release()` is called\n * without an error — even if the connection is still mid-transaction (status\n * `T`/`E`). That happens in practice: drizzle's pool transaction releases in\n * a `finally` after attempting ROLLBACK, and if the ROLLBACK itself fails\n * (e.g. it was queued behind a statement that hit the client-side\n * query_timeout), the client goes back dirty. The next checkout then runs\n * its statements inside the zombie transaction — with the previous request's\n * `app.*` RLS GUCs still applied, which turns unrelated queries into\n * RLS-scoped ones (observed in production as registration failing with\n * SQLSTATE 42501 under a leaked anonymous context).\n *\n * pg-pool emits `release` before it consults its private `_expired` set, so\n * marking the client expired here makes `_release()` destroy it instead of\n * pooling it. Both `client._txStatus` (pg ≥ 8.16) and `pool._expired` are\n * private APIs — feature-detect and fall back to loud logging so an upstream\n * change degrades to observability, never to silent corruption.\n */\nexport function guardPoolAgainstDirtyRelease(pool: Pool, label: string): void {\n pool.on(\"release\", (err: Error | undefined, client: unknown) => {\n if (err) return; // errored clients are already destroyed by pg-pool\n const txStatus = (client as { _txStatus?: string | null })?._txStatus;\n if (typeof txStatus !== \"string\" || txStatus === TX_IDLE) return;\n\n // pg-pool keeps expired clients in a WeakSet (a plain object works too\n // if upstream ever changes it — duck-type on add/has).\n const expired = (pool as unknown as { _expired?: { add(c: object): unknown; has(c: object): boolean } })._expired;\n if (expired && typeof expired.add === \"function\" && typeof expired.has === \"function\" && client && typeof client === \"object\") {\n expired.add(client);\n logger.error(\n `[${label}] Client released back to the pool while still in a transaction ` +\n `(status '${txStatus}') — destroying it so the open transaction and its ` +\n `session state (RLS GUCs) cannot leak into the next request.`\n );\n } else {\n logger.error(\n `[${label}] Client released mid-transaction (status '${txStatus}') but the ` +\n `pool's internal expiry set is unavailable (pg-pool internals changed?). ` +\n `The connection may leak its open transaction into subsequent requests.`\n );\n }\n });\n}\n\n/**\n * Create a Drizzle-backed Postgres connection with a production-grade\n * connection pool.\n *\n * @param connectionString Postgres connection URL\n * @param schema Optional Drizzle schema for the relational API\n * @param poolConfig Optional pool tuning (merged over defaults)\n * @param env Where the pool-size ceiling `REBASE_DB_POOL_MAX`\n * is read from (see {@link poolMaxCeiling}); pass\n * `{}` to read none.\n *\n * @returns `{ db, pool, connectionString }` — the `pool` is exposed so\n * callers can register shutdown hooks (`pool.end()`) or monitor\n * pool metrics.\n */\nexport function createPostgresDatabaseConnection(\n connectionString: string,\n schema?: Record<string, unknown>,\n poolConfig?: PostgresPoolConfig,\n env: NodeJS.ProcessEnv = process.env\n) {\n const opts = { ...DEFAULT_POOL,\n...poolConfig };\n connectionString = pinSearchPath(connectionString, opts.searchPath);\n\n const pgPoolConfig: PoolConfig = {\n connectionString,\n max: cappedPoolMax(opts.max, env),\n idleTimeoutMillis: opts.idleTimeoutMillis,\n connectionTimeoutMillis: opts.connectionTimeoutMillis,\n query_timeout: opts.queryTimeout,\n statement_timeout: opts.statementTimeout,\n keepAlive: opts.keepAlive,\n keepAliveInitialDelayMillis: 0\n };\n\n const pool = new Pool(pgPoolConfig);\n\n // ── Pool event logging ────────────────────────────────────────────────\n // Uses console.* because the structured logger lives in server\n // (a separate package). The caller can replace these with the structured\n // logger if desired via pool.on() after creation.\n pool.on(\"error\", (err) => {\n reportPoolError(\"pg-pool\", err);\n if (err.message.includes(\"ETIMEDOUT\")) {\n logger.warn(\"[pg-pool] Connection timeout detected — pool will auto-retry\");\n }\n });\n guardPoolAgainstDirtyRelease(pool, \"pg-pool\");\n\n // Create drizzle instance — pass schema when available to enable db.query relational API\n const db = schema ? drizzle(pool, { schema }) : drizzle(pool);\n\n return { db,\npool,\nconnectionString };\n}\n\n/**\n * Create a direct (non-pooled) connection for operations that require\n * session-level features incompatible with PgBouncer transaction mode,\n * such as LISTEN/NOTIFY, prepared statements, or advisory locks.\n *\n * Uses a smaller pool since this is only for specific use cases.\n */\nexport function createDirectDatabaseConnection(\n connectionString: string,\n schema?: Record<string, unknown>,\n poolConfig?: PostgresPoolConfig\n) {\n const opts = {\n ...DEFAULT_POOL,\n max: 5,\n ...poolConfig\n };\n connectionString = pinSearchPath(connectionString, opts.searchPath);\n\n const pgPoolConfig: PoolConfig = {\n connectionString,\n max: cappedPoolMax(opts.max),\n idleTimeoutMillis: opts.idleTimeoutMillis,\n connectionTimeoutMillis: opts.connectionTimeoutMillis,\n query_timeout: opts.queryTimeout,\n statement_timeout: opts.statementTimeout,\n keepAlive: opts.keepAlive,\n keepAliveInitialDelayMillis: 0\n };\n\n const pool = new Pool(pgPoolConfig);\n\n pool.on(\"error\", (err) => reportPoolError(\"pg-direct-pool\", err));\n guardPoolAgainstDirtyRelease(pool, \"pg-direct-pool\");\n\n const db = schema ? drizzle(pool, { schema }) : drizzle(pool);\n\n return { db,\npool,\nconnectionString };\n}\n\n/**\n * Create a read-only connection for routing read queries to replicas.\n * Uses a moderate pool size since reads are distributed across replicas.\n */\nexport function createReadReplicaConnection(\n connectionString: string,\n schema?: Record<string, unknown>,\n poolConfig?: PostgresPoolConfig\n) {\n const opts = {\n ...DEFAULT_POOL,\n max: 10,\n ...poolConfig\n };\n connectionString = pinSearchPath(connectionString, opts.searchPath);\n\n const pgPoolConfig: PoolConfig = {\n connectionString,\n max: cappedPoolMax(opts.max),\n idleTimeoutMillis: opts.idleTimeoutMillis,\n connectionTimeoutMillis: opts.connectionTimeoutMillis,\n query_timeout: opts.queryTimeout,\n statement_timeout: opts.statementTimeout,\n keepAlive: opts.keepAlive,\n keepAliveInitialDelayMillis: 0\n };\n\n const pool = new Pool(pgPoolConfig);\n\n pool.on(\"error\", (err) => reportPoolError(\"pg-replica-pool\", err));\n guardPoolAgainstDirtyRelease(pool, \"pg-replica-pool\");\n\n const db = schema ? drizzle(pool, { schema }) : drizzle(pool);\n\n return { db,\npool,\nconnectionString };\n}\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { createRequire as __createRequire } from "module";
2
2
  __createRequire(import.meta.url);
3
- import { n as __exportAll } from "./rolldown-runtime-V7rFbMRe.js";
4
- import { c as pinSearchPath, o as defaultPoolTimeouts, s as guardPoolAgainstDirtyRelease, t as cappedPoolMax } from "./connection-C938nR3E.js";
3
+ import { i as __exportAll } from "./cli-prompt-DXG0yk-h.js";
4
+ import { c as pinSearchPath, o as defaultPoolTimeouts, s as guardPoolAgainstDirtyRelease, t as cappedPoolMax, u as reportPoolError } from "./connection-czvI7TZL.js";
5
5
  import { Pool } from "pg";
6
6
  import { drizzle } from "drizzle-orm/node-postgres";
7
7
  import { logger } from "@rebasepro/server";
@@ -39,9 +39,7 @@ var DatabasePoolManager = class {
39
39
  allowExitOnIdle: true,
40
40
  ...defaultPoolTimeouts()
41
41
  });
42
- pool.on("error", (err) => {
43
- logger.error(`[DatabasePoolManager] Unexpected error on idle client for db ${databaseName}`, { error: err });
44
- });
42
+ pool.on("error", (err) => reportPoolError(`DatabasePoolManager:${databaseName}`, err));
45
43
  guardPoolAgainstDirtyRelease(pool, `pg-pool:${databaseName}`);
46
44
  this.pools.set(databaseName, pool);
47
45
  return pool;
@@ -116,4 +114,4 @@ var DatabasePoolManager = class {
116
114
  //#endregion
117
115
  export { databasePoolManager_exports as n, DatabasePoolManager as t };
118
116
 
119
- //# sourceMappingURL=databasePoolManager-Yqdw14wY.js.map
117
+ //# sourceMappingURL=databasePoolManager-3stMNFL-.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"databasePoolManager-3stMNFL-.js","names":[],"sources":["../src/databasePoolManager.ts"],"sourcesContent":["import { Pool } from \"pg\";\nimport { drizzle } from \"drizzle-orm/node-postgres\";\nimport { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport { logger } from \"@rebasepro/server\";\nimport { guardPoolAgainstDirtyRelease, pinSearchPath, cappedPoolMax, defaultPoolTimeouts } from \"./connection\";\nimport { reportPoolError } from \"./pool-disconnects\";\n\nexport class DatabasePoolManager {\n private pools: Map<string, Pool> = new Map();\n private drizzleInstances: Map<string, NodePgDatabase> = new Map();\n public readonly defaultDatabaseName: string;\n private readonly rootConnectionString: string;\n\n constructor(adminConnectionString: string) {\n this.rootConnectionString = adminConnectionString;\n try {\n const url = new URL(adminConnectionString);\n this.defaultDatabaseName = url.pathname.slice(1);\n } catch (e) {\n throw new Error(`Invalid adminConnectionString provided: ${e}`);\n }\n }\n\n public getDrizzle(databaseName: string): NodePgDatabase<Record<string, never>> {\n const existing = this.drizzleInstances.get(databaseName);\n if (existing) {\n return existing;\n }\n\n const pool = this.getPool(databaseName);\n const db = drizzle(pool);\n this.drizzleInstances.set(databaseName, db);\n return db;\n }\n\n public getPool(databaseName: string): Pool {\n if (this.pools.has(databaseName)) {\n return this.pools.get(databaseName)!;\n }\n\n const url = new URL(this.rootConnectionString);\n url.pathname = `/${databaseName}`;\n\n const pool = new Pool({\n // Same pin as the primary pool: these are branch/multi-database\n // connections to the *same* server, so they inherit the same\n // `\"$user\"` hazard. See `pinSearchPath`.\n connectionString: pinSearchPath(url.toString()),\n // Capped by REBASE_DB_POOL_MAX, which the managed development\n // database sets to 1: PGlite multiplexes onto a single session and\n // overlapping transactions deadlock there.\n max: cappedPoolMax(10),\n idleTimeoutMillis: 10000, // Reduced from 30000 for aggressive cleanup\n allowExitOnIdle: true, // Prevent idle clients from hanging the Node.js process\n // The main pool's limits: a full pool answers 503 after 10 s rather\n // than queueing forever, and a statement stops at 30 s server-side.\n ...defaultPoolTimeouts()\n });\n\n // Prevent idle client errors from crashing the Node.js process\n pool.on(\"error\", (err) => reportPoolError(`DatabasePoolManager:${databaseName}`, err));\n guardPoolAgainstDirtyRelease(pool, `pg-pool:${databaseName}`);\n\n this.pools.set(databaseName, pool);\n return pool;\n }\n\n /**\n * Disconnect and remove the pool for a specific database.\n * Required before `CREATE DATABASE ... TEMPLATE` or `DROP DATABASE`,\n * which need exclusive access to the target database.\n */\n public async disconnectDatabase(databaseName: string): Promise<void> {\n const pool = this.pools.get(databaseName);\n if (pool) {\n await pool.end();\n this.pools.delete(databaseName);\n this.drizzleInstances.delete(databaseName);\n }\n }\n\n /** Check if a pool exists for a given database name. */\n public hasPool(databaseName: string): boolean {\n return this.pools.has(databaseName);\n }\n\n /**\n * Does this server serve exactly one database?\n *\n * The managed development database is PGlite, and PGlite serves exactly\n * one. `CREATE DATABASE \"rb_feature\" TEMPLATE \"postgres\"` there writes a\n * `pg_database` catalogue row and nothing else: nothing errors, the row is\n * listable, `pg_database_size` answers — and connecting to the \"branch\"\n * lands you in the parent. Every write made in the belief that it is\n * sandboxed goes to the developer's real development database.\n *\n * The CLI already refuses `rebase db branch` on this ground, but it does it\n * by parsing argv, so Studio — which reaches `BranchService` over the\n * websocket — walked straight past it. The connection is the only thing\n * both callers share, so the answer belongs here.\n *\n * Measured, not assumed: `select version()` over the PGlite socket returns\n * `PostgreSQL 18.3 (PGlite 0.5.6) on wasm32-…`, which is how a wasm build\n * announces itself and what `dev-db/constraints.ts` documents. A real\n * Postgres never carries `(PGlite `.\n *\n * Cached, because a live connection cannot change engine underneath us, and\n * a `PostgresBackendDriver` — which builds a `BranchService` — is\n * constructed per transaction.\n */\n public servesOneDatabase(): Promise<boolean> {\n this.oneDatabaseProbe ??= this.probeOneDatabase();\n return this.oneDatabaseProbe;\n }\n\n private oneDatabaseProbe?: Promise<boolean>;\n\n private async probeOneDatabase(): Promise<boolean> {\n try {\n const result = await this.getPool(this.defaultDatabaseName)\n .query<{ version: string }>(\"SELECT version() AS version\");\n return /\\(PGlite\\b/i.test(result.rows[0]?.version ?? \"\");\n } catch (err) {\n // Fail open, and do not cache the failure. The cost of a wrong\n // \"yes\" is refusing branching on a real Postgres that momentarily\n // could not answer; the cost of a wrong \"no\" is one fake branch,\n // which is what this exists to prevent — but a server that cannot\n // run `select version()` is not one we can conclude anything about.\n this.oneDatabaseProbe = undefined;\n logger.warn(\"[DatabasePoolManager] Could not read the server version\", { error: err });\n return false;\n }\n }\n\n public async shutdown(): Promise<void> {\n const promises = [];\n for (const [dbName, pool] of this.pools.entries()) {\n // Debug: every CLI command that reaches a database ends here, and\n // the reader of `rebase db branch create` did not ask about pools.\n logger.debug(`[DatabasePoolManager] Shutting down pool for ${dbName}`);\n promises.push(pool.end());\n }\n await Promise.all(promises);\n this.pools.clear();\n this.drizzleInstances.clear();\n }\n}\n"],"mappings":";;;;;;;;;AAOA,IAAa,sBAAb,MAAiC;CAC7B,wBAAmC,IAAI,IAAI;CAC3C,mCAAwD,IAAI,IAAI;CAChE;CACA;CAEA,YAAY,uBAA+B;EACvC,KAAK,uBAAuB;EAC5B,IAAI;GACA,MAAM,MAAM,IAAI,IAAI,qBAAqB;GACzC,KAAK,sBAAsB,IAAI,SAAS,MAAM,CAAC;EACnD,SAAS,GAAG;GACR,MAAM,IAAI,MAAM,2CAA2C,GAAG;EAClE;CACJ;CAEA,WAAkB,cAA6D;EAC3E,MAAM,WAAW,KAAK,iBAAiB,IAAI,YAAY;EACvD,IAAI,UACA,OAAO;EAIX,MAAM,KAAK,QADE,KAAK,QAAQ,YACP,CAAI;EACvB,KAAK,iBAAiB,IAAI,cAAc,EAAE;EAC1C,OAAO;CACX;CAEA,QAAe,cAA4B;EACvC,IAAI,KAAK,MAAM,IAAI,YAAY,GAC3B,OAAO,KAAK,MAAM,IAAI,YAAY;EAGtC,MAAM,MAAM,IAAI,IAAI,KAAK,oBAAoB;EAC7C,IAAI,WAAW,IAAI;EAEnB,MAAM,OAAO,IAAI,KAAK;GAIlB,kBAAkB,cAAc,IAAI,SAAS,CAAC;GAI9C,KAAK,cAAc,EAAE;GACrB,mBAAmB;GACnB,iBAAiB;GAGjB,GAAG,oBAAoB;EAC3B,CAAC;EAGD,KAAK,GAAG,UAAU,QAAQ,gBAAgB,uBAAuB,gBAAgB,GAAG,CAAC;EACrF,6BAA6B,MAAM,WAAW,cAAc;EAE5D,KAAK,MAAM,IAAI,cAAc,IAAI;EACjC,OAAO;CACX;;;;;;CAOA,MAAa,mBAAmB,cAAqC;EACjE,MAAM,OAAO,KAAK,MAAM,IAAI,YAAY;EACxC,IAAI,MAAM;GACN,MAAM,KAAK,IAAI;GACf,KAAK,MAAM,OAAO,YAAY;GAC9B,KAAK,iBAAiB,OAAO,YAAY;EAC7C;CACJ;;CAGA,QAAe,cAA+B;EAC1C,OAAO,KAAK,MAAM,IAAI,YAAY;CACtC;;;;;;;;;;;;;;;;;;;;;;;;;CA0BA,oBAA6C;EACzC,KAAK,qBAAqB,KAAK,iBAAiB;EAChD,OAAO,KAAK;CAChB;CAEA;CAEA,MAAc,mBAAqC;EAC/C,IAAI;GACA,MAAM,SAAS,MAAM,KAAK,QAAQ,KAAK,mBAAmB,CAAC,CACtD,MAA2B,6BAA6B;GAC7D,OAAO,cAAc,KAAK,OAAO,KAAK,EAAE,EAAE,WAAW,EAAE;EAC3D,SAAS,KAAK;GAMV,KAAK,mBAAmB,KAAA;GACxB,OAAO,KAAK,2DAA2D,EAAE,OAAO,IAAI,CAAC;GACrF,OAAO;EACX;CACJ;CAEA,MAAa,WAA0B;EACnC,MAAM,WAAW,CAAC;EAClB,KAAK,MAAM,CAAC,QAAQ,SAAS,KAAK,MAAM,QAAQ,GAAG;GAG/C,OAAO,MAAM,gDAAgD,QAAQ;GACrE,SAAS,KAAK,KAAK,IAAI,CAAC;EAC5B;EACA,MAAM,QAAQ,IAAI,QAAQ;EAC1B,KAAK,MAAM,MAAM;EACjB,KAAK,iBAAiB,MAAM;CAChC;AACJ"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Throw what the driver would say of a `rebase db …` line it refuses on its
3
+ * flags, before it would connect: an option the command does not take, and
4
+ * the checks `db backup` and `db restore` make of their own line (a stray
5
+ * word, a pattern that leaves out the rebase schema).
6
+ *
7
+ * Reads no environment and opens nothing, so it is safe to run before a
8
+ * database exists.
9
+ *
10
+ * @param args the driver's line: `["db", "<subcommand>", …]`, without the
11
+ * CLI's own words in front. A line for a subcommand this driver does not
12
+ * answer passes, for the dispatch to refuse.
13
+ */
14
+ export declare function assertDbCommandLine(args: readonly string[]): void;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The `db` subcommands this driver dispatches, with no runtime dependencies.
3
+ *
4
+ * Its own module so the dispatch in `cli.ts` can name them without loading
5
+ * what judges their lines (`db-command-line.ts` reaches the backup module,
6
+ * which `cli.ts` imports only when a backup command runs).
7
+ */
8
+ export declare const DRIVER_DB_SUBCOMMANDS: readonly ["push", "generate", "migrate", "branch", "backup", "restore", "backups"];
9
+ export type DriverDbSubcommand = typeof DRIVER_DB_SUBCOMMANDS[number];
10
+ /** Is `word` a `db` subcommand this driver answers? */
11
+ export declare function isDriverDbSubcommand(word: string | undefined): word is DriverDbSubcommand;
@@ -1,13 +1,13 @@
1
1
  import { createRequire as __createRequire } from "module";
2
2
  __createRequire(import.meta.url);
3
- import { n as __exportAll } from "./rolldown-runtime-V7rFbMRe.js";
3
+ import { i as __exportAll } from "./cli-prompt-DXG0yk-h.js";
4
4
  import { h as searchIndexStatements, n as SEARCH_TEXT_FN, r as SEARCH_UNACCENT_FN, u as searchColumnStamps } from "./search-column-BM-GV6vH.js";
5
- import { n as extractPgError } from "./pg-error-utils-Bv5n2PLn.js";
6
- import { u as vectorIndexStatement } from "./vector-index-BFhyEaiz.js";
7
- import { _ as sortIndexSpecs, l as collectionIndexStatement } from "./source-Bv5KPeIx.js";
8
- import { a as triggerStatements, i as SET_UPDATED_AT_FN, r as quoteSqlLiteral } from "./plan-schema-CXOucCw-.js";
9
- import { n as renderEnableRls, r as renderPgType, s as renderPrimaryKeyConstraint, t as renderColumnDefinition } from "./render-ddl-CQNwZdse.js";
10
- import { a as isLockTimeout, c as resolveBootLockOptions, l as sleep, o as isMultiStatementRefusal, r as atomicBatch } from "./boot-ddl-s_3Hkz94.js";
5
+ import { n as extractPgError } from "./pg-error-utils-DhiUTlVx.js";
6
+ import { u as vectorIndexStatement } from "./vector-index-DsQ3V2d2.js";
7
+ import { _ as sortIndexSpecs, l as collectionIndexStatement } from "./source-fZb92pN8.js";
8
+ import { a as triggerStatements, i as SET_UPDATED_AT_FN, r as quoteSqlLiteral } from "./plan-schema-Czf3q4-Q.js";
9
+ import { n as renderEnableRls, r as renderPgType, s as renderPrimaryKeyConstraint, t as renderColumnDefinition } from "./render-ddl-4v88y-Gv.js";
10
+ import { a as isLockTimeout, c as resolveBootLockOptions, l as sleep, o as isMultiStatementRefusal, r as atomicBatch } from "./boot-ddl-EsgqDkx0.js";
11
11
  import { logger } from "@rebasepro/server";
12
12
  //#region src/schema/column-type-drift.ts
13
13
  /**
@@ -1048,4 +1048,4 @@ function scheduleDeferredIndexBuild(openSession, indexes, options = {}) {
1048
1048
  //#endregion
1049
1049
  export { assertSafeIdentifier as a, helperFunctionKey as c, HELPER_FUNCTIONS as i, columnTypeDriftMessage as l, buildDeferredIndexes as n, describeHelperFunction as o, deferred_index_build_exports as r, diffPlanAgainstCatalogue as s, TABLE_BUSY_REASON as t, typesAgree as u };
1050
1050
 
1051
- //# sourceMappingURL=deferred-index-build-Cis_HoGn.js.map
1051
+ //# sourceMappingURL=deferred-index-build-CqaOv1Xx.js.map