@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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"branch-diff-CxE4VlxT.js","names":[],"sources":["../src/branching/branch-diff.ts"],"sourcesContent":["/**\n * What a database branch changed, and applying it to the database it was made\n * from.\n *\n * A branch is a `CREATE DATABASE … TEMPLATE` copy (see `BranchService`): a\n * database of its own, on the same server. Two databases cannot be joined in\n * one query, so each side is read on its own through `SQLAdmin.executeSql`\n * with `database`, and compared here.\n *\n * ## Which side changed a row\n *\n * Comparing the two databases says which rows differ, not who changed them: a\n * row the target inserted after the branch was made looks exactly like a row\n * the branch deleted. A merge that took every difference as the branch's would\n * delete everything the target wrote meanwhile.\n *\n * Transaction ids are the server's, not a database's, and the template copy\n * keeps every row's `xmin`. So a row written after the branch was made — on\n * either side — has an `xmin` younger than the transaction that created the\n * branch database. That is the fork point: the `xmin` of the new database's\n * `pg_database` row, read when the branch is made and kept in its record\n * (`BranchInfo.forkXid`), because a `GRANT`, an `ALTER DATABASE` or a forced\n * copy from the branch rewrites that row later. With it each difference has an\n * author:\n *\n * | branch row | target row | the change |\n * |---------------------|---------------------|--------------------------------|\n * | younger | absent | the branch inserted it |\n * | absent | older | the branch deleted it |\n * | younger | older | the branch updated it |\n * | older or absent | younger or absent | the target changed it: kept |\n * | younger | younger | both changed it: a conflict |\n *\n * Two cases cannot be told apart and are read the way that loses nothing: a\n * row the branch deleted and the target then changed is the target's change,\n * and kept; a row the target deleted and the branch then changed is the\n * branch's insert. An update that wrote the same values still counts as a\n * write, so it can make a conflict of a row only one side meant to change.\n *\n * A delete reaches further than its row. A row the branch deleted can be\n * referenced, through a foreign key, by a row the target wrote since — a post\n * by an author the branch removed. Deleting it on the target would cascade\n * into that post, null its column, or fail outright, so it is a conflict, not\n * a delete: `onConflict: \"target\"` keeps the row, `\"branch\"` deletes it and\n * says what the foreign key reached (`referencedBy` on the diff).\n *\n * A branch recorded before the fork point was kept falls back to the\n * `pg_database` row as it is now. When the fork point cannot be read — that\n * row was frozen by a vacuum — every difference is counted as the branch's and\n * the diff says so (`forkPointKnown: false`).\n *\n * ## What a merge does\n *\n * See `BranchMergeOptions` in `@rebasepro/types`. In short: the branch's\n * inserts, updates and deletes, in one statement on the target, all or none;\n * the target's own changes kept; nothing at all when either side moved since\n * the reviewed diff. Schema and policies are never merged.\n */\nimport { createHash } from \"node:crypto\";\nimport {\n isBranchAdmin,\n isSQLAdmin,\n type BranchAdmin,\n type BranchDiff,\n type BranchDiffOptions,\n type BranchMergeBlock,\n type BranchMergeOptions,\n type BranchMergeResult,\n type BranchReferencingRows,\n type BranchRowChange,\n type BranchSchemaChange,\n type BranchTableDataDiff,\n type DatabaseAdmin,\n type SQLAdmin\n} from \"@rebasepro/types\";\nimport { isRebaseInternalTable } from \"@rebasepro/common\";\nimport { ApiError } from \"@rebasepro/server\";\n\n/** Rows shown per table when the caller does not say. */\nexport const DEFAULT_BRANCH_DIFF_SAMPLE_LIMIT = 100;\n\n/** The most rows a diff samples per table, whatever the caller asks for. */\nexport const MAX_BRANCH_DIFF_SAMPLE_LIMIT = 1000;\n\n/** What a sample shows in place of a secret's value. */\nexport const REDACTED = \"[redacted]\";\n\n/**\n * Columns whose values a sample never shows: the auth users table's password\n * hash and mailed tokens, and anything named like a secret, a token or a hash\n * in any table — a diff is read by whoever reviews a branch, and these are\n * credentials, not data to review. Matched by name, so a users table in\n * another schema, or an app's own `api_secret`, is covered too.\n */\nconst SECRET_COLUMN = /password|secret|(^|_)token$|_token_hash$|(^|_)hash$/i;\n\n/** `row` with every secret column's value replaced by {@link REDACTED}. */\nfunction redacted(row: Record<string, string | null> | undefined): Record<string, string | null> | undefined {\n if (!row) return row;\n const shown: Record<string, string | null> = {};\n for (const [column, value] of Object.entries(row)) shown[column] = value !== null && SECRET_COLUMN.test(column) ? REDACTED : value;\n return shown;\n}\n\n/** Rows a table may hold, per side, to be matched row by row, when the caller does not say. */\nexport const DEFAULT_BRANCH_DIFF_MAX_ROWS = 100_000;\n\n/** The age Postgres gives a frozen or permanent transaction id. */\nconst FROZEN_AGE = 2147483647;\n\n/**\n * Every refusal here is an `ApiError` the caller can act on — the status a\n * route answers it with, and the tables it is about in `details.tables` — so\n * none logs as a warning. Thrown with its code and status written out at the\n * call, where the error-code reference's scan reads them.\n */\nconst EXPECTED = true;\n\n/** A table a refusal is about: the code `blocked` carried, or the reason it conflicts. */\ninterface RefusedTable {\n schema: string;\n table: string;\n code?: BranchMergeBlock[\"code\"];\n reason: string;\n}\n\n// ── Reading rows ─────────────────────────────────────────────────────────────\n\ntype Row = Record<string, unknown>;\n\nfunction text(row: Row, key: string): string {\n const value = row[key];\n if (typeof value === \"string\") return value;\n return value === null || value === undefined ? \"\" : String(value);\n}\n\nfunction nullableText(row: Row, key: string): string | null {\n const value = row[key];\n if (value === null || value === undefined) return null;\n return typeof value === \"string\" ? value : String(value);\n}\n\nfunction flag(row: Row, key: string): boolean {\n const value = row[key];\n return value === true || value === \"t\" || value === \"true\";\n}\n\nfunction stringList(json: string): string[] {\n const parsed: unknown = JSON.parse(json || \"[]\");\n return Array.isArray(parsed) ? parsed.filter((item): item is string => typeof item === \"string\") : [];\n}\n\n/** A row as `json_object_agg` over `json_each_text` writes it: every value text or null. */\nfunction textRecord(json: string): Record<string, string | null> {\n const parsed: unknown = JSON.parse(json);\n const record: Record<string, string | null> = {};\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) return record;\n for (const [key, value] of Object.entries(parsed)) {\n record[key] = typeof value === \"string\" ? value : null;\n }\n return record;\n}\n\nconst md5 = (value: string): string => createHash(\"md5\").update(value, \"utf8\").digest(\"hex\");\n\n// ── SQL text ─────────────────────────────────────────────────────────────────\n\nfunction ident(name: string): string {\n return `\"${name.replace(/\"/g, \"\\\"\\\"\")}\"`;\n}\n\nfunction qualified(schema: string, table: string): string {\n return `${ident(schema)}.${ident(table)}`;\n}\n\n/**\n * The schemas a query reads, given `$1`: the ones asked for, or every schema\n * but the system's.\n */\nfunction schemaFilter(column: string): string {\n return `(CASE WHEN $1::text[] IS NULL\n THEN ${column} NOT LIKE 'pg\\\\_%' AND ${column} <> 'information_schema'\n ELSE ${column} = ANY($1::text[]) END)`;\n}\n\n/** The primary key of `t` as a JSON array: unique, and lossless for any type. */\nfunction keyExpression(primaryKey: readonly string[]): string {\n return `json_build_array(${primaryKey.map((column) => `t.${ident(column)}`).join(\", \")})::text`;\n}\n\n/**\n * `t.<column>` as text that reads the same in any session.\n *\n * A row's text output follows the session's settings, and a database-level\n * `ALTER DATABASE … SET timezone` (or `datestyle`) is not copied to a branch\n * made from it — so the same `timestamptz` wrote different text on the two\n * sides, and every untouched row read as changed. Times, timestamps and dates\n * are written out with `to_char` in UTC instead; infinities keep their own\n * words, which no setting changes.\n */\nfunction settledValue(column: { name: string; type: string }): string {\n const value = `t.${ident(column.name)}`;\n const finite = (expression: string, format: string) =>\n `CASE WHEN isfinite(${value}) THEN to_char(${expression}, '${format}') ELSE ${value}::text END`;\n if (column.type === \"timestamp with time zone\" || (column.type.startsWith(\"timestamp(\") && column.type.endsWith(\"with time zone\"))) {\n return finite(`${value} AT TIME ZONE 'UTC'`, \"YYYY-MM-DD\\\"T\\\"HH24:MI:SS.US\");\n }\n if (column.type === \"timestamp without time zone\" || column.type.startsWith(\"timestamp(\")) {\n return finite(value, \"YYYY-MM-DD\\\"T\\\"HH24:MI:SS.US\");\n }\n if (column.type === \"date\") return finite(value, \"YYYY-MM-DD\");\n return value;\n}\n\n/**\n * A digest of `t`'s values in `columns`. A row constructor rather than a\n * function call, which would stop at 100 columns. The same columns, of the\n * same types, write the same text on both sides, whatever either session's\n * timezone or date style.\n */\nfunction hashExpression(columns: readonly { name: string; type: string }[]): string {\n return `md5(ROW(${columns.map(settledValue).join(\", \")})::text)`;\n}\n\n// ── The catalogue ────────────────────────────────────────────────────────────\n\nexport interface ColumnFacts {\n name: string;\n type: string;\n notNull: boolean;\n defaultExpression: string | null;\n /** `s` for a stored generated column. */\n generated: string;\n /** `a` for GENERATED ALWAYS, `d` for BY DEFAULT. */\n identity: string;\n sequence: string | null;\n}\n\nexport interface PolicyFacts {\n name: string;\n permissive: string;\n roles: string[];\n command: string;\n using: string | null;\n withCheck: string | null;\n}\n\n/** An object of a table — a constraint, an index, a trigger — as the SQL that declares it. */\nexport interface NamedDefinition {\n name: string;\n definition: string;\n}\n\nexport interface TableFacts {\n schema: string;\n table: string;\n rls: boolean;\n forceRls: boolean;\n columns: ColumnFacts[];\n primaryKey: string[];\n policies: PolicyFacts[];\n /** Unique, foreign-key, check and exclusion constraints: everything but the primary key and NOT NULL. */\n constraints?: NamedDefinition[];\n /** Indexes that back no constraint — a constraint's own index comes and goes with it. */\n indexes?: NamedDefinition[];\n /** Triggers someone wrote: not the ones Postgres makes for a foreign key. */\n triggers?: NamedDefinition[];\n}\n\n/** Tables by `schema.table`, as a JSON pair so no name can collide with another. */\nexport type Catalog = Map<string, TableFacts>;\n\n/** An object of a schema rather than a table: an enum type, a view, a function. */\nexport interface SchemaObjectFacts {\n object: \"enum\" | \"view\" | \"function\";\n schema: string;\n /** Its name; a function's carries its argument types, as overloads differ by them. */\n name: string;\n definition: string;\n}\n\n/** Schema objects by kind, schema and name. */\nexport type SchemaObjects = Map<string, SchemaObjectFacts>;\n\nconst tableKey = (schema: string, table: string): string => JSON.stringify([schema, table]);\n\nconst TABLE_FILTER = `c.relkind IN ('r', 'p') AND NOT c.relispartition\n AND NOT EXISTS (SELECT 1 FROM pg_depend dep\n WHERE dep.classid = 'pg_class'::regclass AND dep.objid = c.oid AND dep.deptype = 'e')`;\n\nconst TABLES_SQL = `\nSELECT n.nspname AS schema_name, c.relname AS table_name,\n c.relrowsecurity AS rls, c.relforcerowsecurity AS force_rls\n FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace\n WHERE ${TABLE_FILTER} AND ${schemaFilter(\"n.nspname\")}`;\n\nconst COLUMNS_SQL = `\nSELECT n.nspname AS schema_name, c.relname AS table_name, a.attname AS column_name,\n format_type(a.atttypid, a.atttypmod) AS data_type, a.attnotnull AS not_null,\n pg_get_expr(ad.adbin, ad.adrelid) AS default_expr,\n a.attgenerated::text AS generated, a.attidentity::text AS identity,\n pg_get_serial_sequence(format('%I.%I', n.nspname, c.relname), a.attname) AS sequence_name\n FROM pg_attribute a\n JOIN pg_class c ON c.oid = a.attrelid\n JOIN pg_namespace n ON n.oid = c.relnamespace\n LEFT JOIN pg_attrdef ad ON ad.adrelid = a.attrelid AND ad.adnum = a.attnum\n WHERE a.attnum > 0 AND NOT a.attisdropped AND ${TABLE_FILTER} AND ${schemaFilter(\"n.nspname\")}\n ORDER BY n.nspname, c.relname, a.attnum`;\n\nconst PRIMARY_KEYS_SQL = `\nSELECT n.nspname AS schema_name, c.relname AS table_name,\n json_agg(a.attname::text ORDER BY k.ord)::text AS columns\n FROM pg_index i\n JOIN pg_class c ON c.oid = i.indrelid\n JOIN pg_namespace n ON n.oid = c.relnamespace\n CROSS JOIN LATERAL unnest(i.indkey::int2[]) WITH ORDINALITY AS k(attnum, ord)\n JOIN pg_attribute a ON a.attrelid = c.oid AND a.attnum = k.attnum\n WHERE i.indisprimary AND ${schemaFilter(\"n.nspname\")}\n GROUP BY n.nspname, c.relname`;\n\nconst POLICIES_SQL = `\nSELECT schemaname::text AS schema_name, tablename::text AS table_name, policyname::text AS policy_name,\n permissive, array_to_json(roles)::text AS roles, cmd, qual, with_check\n FROM pg_policies\n WHERE ${schemaFilter(\"schemaname::text\")}\n ORDER BY schemaname, tablename, policyname`;\n\nconst CONSTRAINTS_SQL = `\nSELECT n.nspname AS schema_name, c.relname AS table_name, con.conname AS object_name,\n pg_get_constraintdef(con.oid) AS definition\n FROM pg_constraint con\n JOIN pg_class c ON c.oid = con.conrelid\n JOIN pg_namespace n ON n.oid = c.relnamespace\n WHERE con.contype IN ('u', 'f', 'c', 'x') AND ${TABLE_FILTER} AND ${schemaFilter(\"n.nspname\")}\n ORDER BY n.nspname, c.relname, con.conname`;\n\nconst INDEXES_SQL = `\nSELECT n.nspname AS schema_name, c.relname AS table_name, i.relname AS object_name,\n pg_get_indexdef(i.oid) AS definition\n FROM pg_index x\n JOIN pg_class i ON i.oid = x.indexrelid\n JOIN pg_class c ON c.oid = x.indrelid\n JOIN pg_namespace n ON n.oid = c.relnamespace\n WHERE NOT EXISTS (SELECT 1 FROM pg_constraint con WHERE con.conindid = x.indexrelid AND con.contype IN ('p', 'u', 'x'))\n AND ${TABLE_FILTER} AND ${schemaFilter(\"n.nspname\")}\n ORDER BY n.nspname, c.relname, i.relname`;\n\nconst TRIGGERS_SQL = `\nSELECT n.nspname AS schema_name, c.relname AS table_name, t.tgname AS object_name,\n pg_get_triggerdef(t.oid) AS definition\n FROM pg_trigger t\n JOIN pg_class c ON c.oid = t.tgrelid\n JOIN pg_namespace n ON n.oid = c.relnamespace\n WHERE NOT t.tgisinternal AND ${TABLE_FILTER} AND ${schemaFilter(\"n.nspname\")}\n ORDER BY n.nspname, c.relname, t.tgname`;\n\n/** Not an extension's: `CREATE EXTENSION` owns those, not a migration. */\nconst notFromExtension = (catalog: string, oid: string) => `NOT EXISTS (SELECT 1 FROM pg_depend dep\n WHERE dep.classid = '${catalog}'::regclass AND dep.objid = ${oid} AND dep.deptype = 'e')`;\n\nconst ENUMS_SQL = `\nSELECT n.nspname AS schema_name, t.typname AS object_name,\n json_agg(e.enumlabel::text ORDER BY e.enumsortorder)::text AS labels\n FROM pg_type t\n JOIN pg_namespace n ON n.oid = t.typnamespace\n JOIN pg_enum e ON e.enumtypid = t.oid\n WHERE ${schemaFilter(\"n.nspname\")} AND ${notFromExtension(\"pg_type\", \"t.oid\")}\n GROUP BY n.nspname, t.typname`;\n\nconst VIEWS_SQL = `\nSELECT n.nspname AS schema_name, c.relname AS object_name, c.relkind::text AS kind,\n pg_get_viewdef(c.oid) AS definition\n FROM pg_class c\n JOIN pg_namespace n ON n.oid = c.relnamespace\n WHERE c.relkind IN ('v', 'm') AND ${schemaFilter(\"n.nspname\")} AND ${notFromExtension(\"pg_class\", \"c.oid\")}`;\n\nconst FUNCTIONS_SQL = `\nSELECT n.nspname AS schema_name,\n p.proname || '(' || pg_get_function_identity_arguments(p.oid) || ')' AS object_name,\n pg_get_functiondef(p.oid) AS definition\n FROM pg_proc p\n JOIN pg_namespace n ON n.oid = p.pronamespace\n WHERE p.prokind IN ('f', 'p') AND ${schemaFilter(\"n.nspname\")} AND ${notFromExtension(\"pg_proc\", \"p.oid\")}`;\n\nconst literal = (value: string): string => `'${value.replace(/'/g, \"''\")}'`;\n\n/** The enum types, views and functions of the schemas a diff reads. */\nasync function readSchemaObjects(sql: SQLAdmin, database: string, schemas: string[] | null): Promise<SchemaObjects> {\n const params = [schemas];\n const [enums, views, functions] = await Promise.all([\n sql.executeSql(ENUMS_SQL, { database, params }),\n sql.executeSql(VIEWS_SQL, { database, params }),\n sql.executeSql(FUNCTIONS_SQL, { database, params })\n ]);\n const objects: SchemaObjects = new Map();\n const add = (facts: SchemaObjectFacts) => objects.set(JSON.stringify([facts.object, facts.schema, facts.name]), facts);\n for (const row of enums) {\n const schema = text(row, \"schema_name\");\n const name = text(row, \"object_name\");\n add({ object: \"enum\", schema, name, definition: `CREATE TYPE ${qualified(schema, name)} AS ENUM (${stringList(text(row, \"labels\")).map(literal).join(\", \")});` });\n }\n for (const row of views) {\n const schema = text(row, \"schema_name\");\n const name = text(row, \"object_name\");\n const kind = text(row, \"kind\") === \"m\" ? \"MATERIALIZED VIEW\" : \"VIEW\";\n add({ object: \"view\", schema, name, definition: `CREATE ${kind} ${qualified(schema, name)} AS\\n${text(row, \"definition\").trim()}` });\n }\n for (const row of functions) {\n add({ object: \"function\", schema: text(row, \"schema_name\"), name: text(row, \"object_name\"), definition: text(row, \"definition\").trim() });\n }\n return objects;\n}\n\nasync function readCatalog(sql: SQLAdmin, database: string, schemas: string[] | null): Promise<Catalog> {\n const params = [schemas];\n const [tables, columns, primaryKeys, policies, constraints, indexes, triggers] = await Promise.all([\n sql.executeSql(TABLES_SQL, { database, params }),\n sql.executeSql(COLUMNS_SQL, { database, params }),\n sql.executeSql(PRIMARY_KEYS_SQL, { database, params }),\n sql.executeSql(POLICIES_SQL, { database, params }),\n sql.executeSql(CONSTRAINTS_SQL, { database, params }),\n sql.executeSql(INDEXES_SQL, { database, params }),\n sql.executeSql(TRIGGERS_SQL, { database, params })\n ]);\n const catalog: Catalog = new Map();\n for (const row of tables) {\n const schema = text(row, \"schema_name\");\n const table = text(row, \"table_name\");\n // Rebase's own plumbing — sessions, history, the branch registry —\n // changes on every request and is nothing a branch is reviewed for.\n if (isRebaseInternalTable(table, schema)) continue;\n catalog.set(tableKey(schema, table), {\n schema,\n table,\n rls: flag(row, \"rls\"),\n forceRls: flag(row, \"force_rls\"),\n columns: [],\n primaryKey: [],\n policies: [],\n constraints: [],\n indexes: [],\n triggers: []\n });\n }\n for (const row of columns) {\n const facts = catalog.get(tableKey(text(row, \"schema_name\"), text(row, \"table_name\")));\n facts?.columns.push({\n name: text(row, \"column_name\"),\n type: text(row, \"data_type\"),\n notNull: flag(row, \"not_null\"),\n defaultExpression: nullableText(row, \"default_expr\"),\n generated: text(row, \"generated\"),\n identity: text(row, \"identity\"),\n sequence: nullableText(row, \"sequence_name\")\n });\n }\n for (const row of primaryKeys) {\n const facts = catalog.get(tableKey(text(row, \"schema_name\"), text(row, \"table_name\")));\n if (facts) facts.primaryKey = stringList(text(row, \"columns\"));\n }\n for (const row of policies) {\n const facts = catalog.get(tableKey(text(row, \"schema_name\"), text(row, \"table_name\")));\n facts?.policies.push({\n name: text(row, \"policy_name\"),\n permissive: text(row, \"permissive\"),\n roles: stringList(text(row, \"roles\")),\n command: text(row, \"cmd\"),\n using: nullableText(row, \"qual\"),\n withCheck: nullableText(row, \"with_check\")\n });\n }\n const named = (rows: Row[], list: (facts: TableFacts) => NamedDefinition[] | undefined) => {\n for (const row of rows) {\n const facts = catalog.get(tableKey(text(row, \"schema_name\"), text(row, \"table_name\")));\n if (facts) list(facts)?.push({ name: text(row, \"object_name\"), definition: text(row, \"definition\") });\n }\n };\n named(constraints, (facts) => facts.constraints);\n named(indexes, (facts) => facts.indexes);\n named(triggers, (facts) => facts.triggers);\n return catalog;\n}\n\n// ── Schema, as SQL ───────────────────────────────────────────────────────────\n\n/** A column as it would be declared in `CREATE TABLE`. */\nexport function columnDefinitionSql(column: ColumnFacts): string {\n const parts = [ident(column.name), column.type];\n if (column.generated === \"s\") {\n parts.push(`GENERATED ALWAYS AS (${column.defaultExpression ?? \"\"}) STORED`);\n } else if (column.identity === \"a\" || column.identity === \"d\") {\n parts.push(`GENERATED ${column.identity === \"a\" ? \"ALWAYS\" : \"BY DEFAULT\"} AS IDENTITY`);\n }\n if (column.notNull) parts.push(\"NOT NULL\");\n if (column.generated !== \"s\" && column.defaultExpression !== null) parts.push(`DEFAULT ${column.defaultExpression}`);\n return parts.join(\" \");\n}\n\nfunction rlsSql(facts: TableFacts): string {\n const table = qualified(facts.schema, facts.table);\n return [\n `ALTER TABLE ${table} ${facts.rls ? \"ENABLE\" : \"DISABLE\"} ROW LEVEL SECURITY;`,\n `ALTER TABLE ${table} ${facts.forceRls ? \"FORCE\" : \"NO FORCE\"} ROW LEVEL SECURITY;`\n ].join(\"\\n\");\n}\n\nexport function tableSql(facts: TableFacts): string {\n const lines = facts.columns.map((column) => ` ${columnDefinitionSql(column)}`);\n if (facts.primaryKey.length > 0) lines.push(` PRIMARY KEY (${facts.primaryKey.map(ident).join(\", \")})`);\n const create = `CREATE TABLE ${qualified(facts.schema, facts.table)} (\\n${lines.join(\",\\n\")}\\n);`;\n return facts.rls || facts.forceRls ? `${create}\\n${rlsSql(facts)}` : create;\n}\n\nexport function policySql(schema: string, table: string, policy: PolicyFacts): string {\n const roles = policy.roles.map((role) => role === \"public\" ? \"public\" : ident(role)).join(\", \");\n const lines = [\n `CREATE POLICY ${ident(policy.name)} ON ${qualified(schema, table)}`,\n ` AS ${policy.permissive}`,\n ` FOR ${policy.command}`,\n ` TO ${roles || \"public\"}`\n ];\n if (policy.using !== null) lines.push(` USING (${policy.using})`);\n if (policy.withCheck !== null) lines.push(` WITH CHECK (${policy.withCheck})`);\n return `${lines.join(\"\\n\")};`;\n}\n\nfunction primaryKeySql(facts: TableFacts): string {\n return facts.primaryKey.length === 0\n ? `-- ${qualified(facts.schema, facts.table)} has no primary key`\n : `ALTER TABLE ${qualified(facts.schema, facts.table)} ADD PRIMARY KEY (${facts.primaryKey.map(ident).join(\", \")});`;\n}\n\n/** A table's constraint, index or trigger, as the SQL that would create it. */\nfunction tableObjectSql(object: \"constraint\" | \"index\" | \"trigger\", facts: TableFacts, item: NamedDefinition): string {\n return object === \"constraint\"\n ? `ALTER TABLE ${qualified(facts.schema, facts.table)} ADD CONSTRAINT ${ident(item.name)} ${item.definition};`\n : `${item.definition};`;\n}\n\nconst TABLE_OBJECTS = [\n [\"constraint\", (facts: TableFacts) => facts.constraints ?? []],\n [\"index\", (facts: TableFacts) => facts.indexes ?? []],\n [\"trigger\", (facts: TableFacts) => facts.triggers ?? []]\n] as const;\n\nconst sameList = (a: readonly string[], b: readonly string[]): boolean =>\n a.length === b.length && a.every((item, index) => item === b[index]);\n\n/**\n * The schema changes from `target` to `branch`: tables, columns, primary keys,\n * constraints, indexes, triggers, row-level security and policies, ordered by\n * table.\n */\nexport function diffCatalogs(target: Catalog, branch: Catalog): BranchSchemaChange[] {\n const changes: BranchSchemaChange[] = [];\n const keys = [...new Set([...target.keys(), ...branch.keys()])].sort();\n for (const key of keys) {\n const before = target.get(key);\n const after = branch.get(key);\n const facts = after ?? before;\n if (!facts) continue;\n const { schema, table } = facts;\n if (!before && after) {\n changes.push({ object: \"table\", change: \"added\", schema, table, after: tableSql(after) });\n } else if (before && !after) {\n changes.push({ object: \"table\", change: \"dropped\", schema, table, before: tableSql(before) });\n } else if (before && after) {\n if (!sameList(before.primaryKey, after.primaryKey)) {\n changes.push({ object: \"table\", change: \"altered\", schema, table, before: primaryKeySql(before), after: primaryKeySql(after) });\n }\n const columnsBefore = new Map(before.columns.map((column) => [column.name, column]));\n const columnsAfter = new Map(after.columns.map((column) => [column.name, column]));\n for (const column of after.columns) {\n const old = columnsBefore.get(column.name);\n const definition = columnDefinitionSql(column);\n if (!old) {\n changes.push({ object: \"column\", change: \"added\", schema, table, name: column.name, after: definition });\n } else if (columnDefinitionSql(old) !== definition) {\n changes.push({ object: \"column\", change: \"altered\", schema, table, name: column.name, before: columnDefinitionSql(old), after: definition });\n }\n }\n for (const column of before.columns) {\n if (!columnsAfter.has(column.name)) {\n changes.push({ object: \"column\", change: \"dropped\", schema, table, name: column.name, before: columnDefinitionSql(column) });\n }\n }\n if (before.rls !== after.rls || before.forceRls !== after.forceRls) {\n changes.push({ object: \"rls\", change: \"altered\", schema, table, before: rlsSql(before), after: rlsSql(after) });\n }\n }\n // A dropped table takes its constraints, indexes and triggers with it;\n // an added one brings them.\n if (after) {\n for (const [object, list] of TABLE_OBJECTS) {\n const old = new Map((before ? list(before) : []).map((item) => [item.name, item]));\n const now = new Map(list(after).map((item) => [item.name, item]));\n for (const [name, item] of now) {\n const was = old.get(name);\n const definition = tableObjectSql(object, after, item);\n if (!was) changes.push({ object, change: \"added\", schema, table, name, after: definition });\n else if (was.definition !== item.definition) {\n changes.push({ object, change: \"altered\", schema, table, name, before: tableObjectSql(object, after, was), after: definition });\n }\n }\n for (const [name, item] of old) {\n if (!now.has(name)) changes.push({ object, change: \"dropped\", schema, table, name, before: tableObjectSql(object, after, item) });\n }\n }\n }\n const policiesBefore = new Map((before?.policies ?? []).map((policy) => [policy.name, policy]));\n const policiesAfter = new Map((after?.policies ?? []).map((policy) => [policy.name, policy]));\n for (const [name, policy] of policiesAfter) {\n const old = policiesBefore.get(name);\n const definition = policySql(schema, table, policy);\n if (!old) {\n changes.push({ object: \"policy\", change: \"added\", schema, table, name, after: definition });\n } else if (policySql(schema, table, old) !== definition) {\n changes.push({ object: \"policy\", change: \"altered\", schema, table, name, before: policySql(schema, table, old), after: definition });\n }\n }\n for (const [name, policy] of policiesBefore) {\n if (!policiesAfter.has(name)) {\n changes.push({ object: \"policy\", change: \"dropped\", schema, table, name, before: policySql(schema, table, policy) });\n }\n }\n }\n return changes;\n}\n\n/** The enum types, views and functions added, dropped or altered from `target` to `branch`. */\nexport function diffSchemaObjects(target: SchemaObjects, branch: SchemaObjects): BranchSchemaChange[] {\n const changes: BranchSchemaChange[] = [];\n for (const key of [...new Set([...target.keys(), ...branch.keys()])].sort()) {\n const before = target.get(key);\n const after = branch.get(key);\n const facts = after ?? before;\n if (!facts) continue;\n const base = { object: facts.object, schema: facts.schema, table: facts.name };\n if (!before && after) changes.push({ ...base, change: \"added\", after: after.definition });\n else if (before && !after) changes.push({ ...base, change: \"dropped\", before: before.definition });\n else if (before && after && before.definition !== after.definition) {\n changes.push({ ...base, change: \"altered\", before: before.definition, after: after.definition });\n }\n }\n return changes;\n}\n\n// ── Rows ─────────────────────────────────────────────────────────────────────\n\n/** One row's key, the digest of its values, and whether it was written after the fork. */\nexport interface RowState {\n key: string;\n hash: string;\n young: boolean;\n}\n\nexport interface RowClassification {\n inserted: string[];\n updated: string[];\n deleted: string[];\n conflicts: string[];\n targetChanges: number;\n}\n\n/**\n * Who changed each row that differs — see the table in the module comment.\n * Without a fork point every difference is the branch's.\n */\nexport function classifyRows(target: readonly RowState[], branch: readonly RowState[], forkPointKnown: boolean): RowClassification {\n const result: RowClassification = { inserted: [], updated: [], deleted: [], conflicts: [], targetChanges: 0 };\n const targetByKey = new Map(target.map((row) => [row.key, row]));\n const branchKeys = new Set<string>();\n for (const row of branch) {\n branchKeys.add(row.key);\n const old = targetByKey.get(row.key);\n const branchWrote = !forkPointKnown || row.young;\n if (!old) {\n if (branchWrote) result.inserted.push(row.key);\n else result.targetChanges += 1;\n continue;\n }\n if (old.hash === row.hash) continue;\n const targetWrote = forkPointKnown && old.young;\n if (branchWrote && !targetWrote) result.updated.push(row.key);\n else if (!branchWrote && targetWrote) result.targetChanges += 1;\n else result.conflicts.push(row.key);\n }\n for (const row of target) {\n if (branchKeys.has(row.key)) continue;\n if (forkPointKnown && row.young) result.targetChanges += 1;\n else result.deleted.push(row.key);\n }\n return result;\n}\n\n/**\n * The sides that rewrote the table since the fork, as far as its rows tell.\n *\n * `ALTER COLUMN … TYPE`, an `ADD COLUMN` with a volatile default, `VACUUM\n * FULL`, `CLUSTER`, `pg_repack`: each writes every row anew, so every row of\n * that side reads as written after the fork, and the provenance the\n * classification rests on is gone. Its mark is a side whose every row is\n * young while some of them hold exactly what the other side holds — rows\n * nobody changed, written again.\n */\nexport function rewrittenSides(target: readonly RowState[], branch: readonly RowState[]): (\"target\" | \"branch\")[] {\n const branchHashes = new Map(branch.map((row) => [row.key, row.hash]));\n if (!target.some((row) => branchHashes.get(row.key) === row.hash)) return [];\n const sides: (\"target\" | \"branch\")[] = [];\n if (target.length > 0 && target.every((row) => row.young)) sides.push(\"target\");\n if (branch.length > 0 && branch.every((row) => row.young)) sides.push(\"branch\");\n return sides;\n}\n\n/** Up to `limit` keys, taken in turn from each kind so that no kind crowds out the rest. */\nexport function pickSamples(kinds: readonly (readonly string[])[], limit: number): string[][] {\n const picked = kinds.map((): string[] => []);\n let remaining = limit;\n for (let index = 0; remaining > 0; index += 1) {\n let took = false;\n for (let kind = 0; kind < kinds.length && remaining > 0; kind += 1) {\n const key = kinds[kind][index];\n if (key === undefined) continue;\n picked[kind].push(key);\n remaining -= 1;\n took = true;\n }\n if (!took) break;\n }\n return picked;\n}\n\n// ── Foreign keys into deleted rows ───────────────────────────────────────────\n\n/** A foreign key that references a table, read from the target. */\ninterface ReferencingKey {\n constraint: string;\n schema: string;\n table: string;\n onDelete: BranchReferencingRows[\"onDelete\"];\n /** The referencing columns, in the key's order. */\n columns: string[];\n /** The columns of the referenced table they point at, in the same order. */\n referenced: string[];\n}\n\nconst ON_DELETE: Record<string, BranchReferencingRows[\"onDelete\"]> = {\n a: \"no action\",\n r: \"restrict\",\n c: \"cascade\",\n n: \"set null\",\n d: \"set default\"\n};\n\n/**\n * Every foreign key that references `$1.$2`, on any table of any schema — a\n * delete reaches a referencing row wherever it is. A partition's copy of a\n * key (`conparentid`) is its parent's, and counted once.\n */\nconst REFERENCING_KEYS_SQL = `\nSELECT con.conname AS constraint_name, cn.nspname AS schema_name, cc.relname AS table_name,\n con.confdeltype::text AS on_delete,\n (SELECT json_agg(a.attname::text ORDER BY k.ord)\n FROM unnest(con.conkey) WITH ORDINALITY AS k(attnum, ord)\n JOIN pg_attribute a ON a.attrelid = con.conrelid AND a.attnum = k.attnum)::text AS columns,\n (SELECT json_agg(a.attname::text ORDER BY k.ord)\n FROM unnest(con.confkey) WITH ORDINALITY AS k(attnum, ord)\n JOIN pg_attribute a ON a.attrelid = con.confrelid AND a.attnum = k.attnum)::text AS referenced\n FROM pg_constraint con\n JOIN pg_class cc ON cc.oid = con.conrelid\n JOIN pg_namespace cn ON cn.oid = cc.relnamespace\n JOIN pg_class pc ON pc.oid = con.confrelid\n JOIN pg_namespace pn ON pn.oid = pc.relnamespace\n WHERE con.contype = 'f' AND con.conparentid = 0 AND pn.nspname = $1 AND pc.relname = $2\n ORDER BY cn.nspname, cc.relname, con.conname`;\n\nasync function referencingKeys(sql: SQLAdmin, database: string, facts: TableFacts): Promise<ReferencingKey[]> {\n const rows = await sql.executeSql(REFERENCING_KEYS_SQL, { database, params: [facts.schema, facts.table] });\n return rows.map((row) => ({\n constraint: text(row, \"constraint_name\"),\n schema: text(row, \"schema_name\"),\n table: text(row, \"table_name\"),\n onDelete: ON_DELETE[text(row, \"on_delete\")] ?? \"no action\",\n columns: stringList(text(row, \"columns\")),\n referenced: stringList(text(row, \"referenced\"))\n }));\n}\n\n/**\n * The rows of `key`'s table, written after the fork, that reference a row of\n * `facts` among the keys in `keysParam` — joined from the referenced table, so\n * the result is per referenced key. `forkParam` is the fork point's\n * transaction id.\n */\nfunction referencingRowsFrom(facts: TableFacts, keyExpr: string, key: ReferencingKey, keysParam: string, forkParam: string): string {\n const join = key.columns.map((column, index) => `c.${ident(column)} = t.${ident(key.referenced[index] ?? column)}`).join(\" AND \");\n return `FROM ${qualified(facts.schema, facts.table)} t\n JOIN ${qualified(key.schema, key.table)} c ON ${join}\n WHERE ${keyExpr} = ANY(${keysParam}::text[])\n AND age(c.xmin) < age(${forkParam}::text::xid)`;\n}\n\n/** What {@link deleteConflicts} found: the keys in conflict, and through which keys. */\ninterface DeleteConflicts {\n keys: string[];\n referencedBy: (BranchReferencingRows & { key: ReferencingKey })[];\n}\n\n/**\n * Of the keys the branch deleted from `facts`, the ones that rows the target\n * wrote after the fork still reference. Read on the target only: on the\n * branch, the delete already went through its foreign keys.\n */\nasync function deleteConflicts(\n sql: SQLAdmin,\n targetDb: string,\n forkXid: string,\n facts: TableFacts,\n keyExpr: string,\n deleted: readonly string[]\n): Promise<DeleteConflicts> {\n const found: DeleteConflicts = { keys: [], referencedBy: [] };\n if (deleted.length === 0) return found;\n const keys = await referencingKeys(sql, targetDb, facts);\n const conflicting = new Set<string>();\n for (const key of keys) {\n const rows = await sql.executeSql(\n `SELECT ${keyExpr} AS k, count(*)::text AS n ${referencingRowsFrom(facts, keyExpr, key, \"$1\", \"$2\")} GROUP BY 1`,\n { database: targetDb, params: [[...deleted], forkXid] }\n );\n if (rows.length === 0) continue;\n let count = 0;\n for (const row of rows) {\n conflicting.add(text(row, \"k\"));\n count += Number(text(row, \"n\"));\n }\n found.referencedBy.push({ schema: key.schema, table: key.table, constraint: key.constraint, onDelete: key.onDelete, rows: count, key });\n }\n found.keys = deleted.filter((rowKey) => conflicting.has(rowKey));\n return found;\n}\n\n/** What a merge needs to know about a table the diff matched row by row. */\ninterface MatchedTable {\n facts: TableFacts;\n keyExpression: string;\n hashExpression: string;\n /** `md5` of every `key:hash` on the target, in `\"C\"` key order — what the merge's guard recomputes. */\n targetDigest: string;\n branchHashes: Map<string, string>;\n /** Who changed each row. `deleted` leaves out {@link deleteConflicts}. */\n rows: RowClassification;\n /** Keys the branch deleted that rows the target wrote since reference: conflicts. */\n deleteConflicts: DeleteConflicts;\n}\n\ninterface ComputedDiff {\n diff: BranchDiff;\n matched: Map<string, MatchedTable>;\n /** The fork point's transaction id, or `null` when it is not known. */\n forkXid: string | null;\n}\n\nasync function rowStates(\n sql: SQLAdmin,\n database: string,\n facts: TableFacts,\n keyExpr: string,\n hashExpr: string,\n forkXid: string | null\n): Promise<RowState[]> {\n const young = forkXid === null\n ? \"false\"\n : \"(age(t.xmin) < age($1::text::xid))\";\n const rows = await sql.executeSql(\n `SELECT ${keyExpr} AS k, ${hashExpr} AS h, ${young} AS young\n FROM ${qualified(facts.schema, facts.table)} t\n ORDER BY (${keyExpr}) COLLATE \"C\"`,\n { database, params: forkXid === null ? undefined : [forkXid] }\n );\n return rows.map((row) => ({ key: text(row, \"k\"), hash: text(row, \"h\"), young: flag(row, \"young\") }));\n}\n\n/** Each table's row count and an order-free checksum of its rows, on one side, in one query. */\nasync function checksums(\n sql: SQLAdmin,\n database: string,\n tables: readonly { facts: TableFacts; keyExpr: string; hashExpr: string }[]\n): Promise<{ count: number; sum: string }[]> {\n if (tables.length === 0) return [];\n const parts = tables.map(({ facts, keyExpr, hashExpr }, index) =>\n `SELECT ${index} AS idx, count(*)::text AS n,\n coalesce(sum(('x' || left(md5(${keyExpr} || ':' || ${hashExpr}), 15))::bit(60)::bigint), 0)::text AS s\n FROM ${qualified(facts.schema, facts.table)} t`);\n const rows = await sql.executeSql(parts.join(\"\\nUNION ALL\\n\"), { database });\n const result = tables.map(() => ({ count: 0, sum: \"0\" }));\n for (const row of rows) {\n const index = Number(text(row, \"idx\"));\n result[index] = { count: Number(text(row, \"n\")), sum: text(row, \"s\") };\n }\n return result;\n}\n\n/**\n * Of one side's rows, how many were written after the fork, and the checksum\n * of the ones written before it.\n */\nasync function forkSplit(\n sql: SQLAdmin,\n database: string,\n facts: TableFacts,\n keyExpr: string,\n hashExpr: string,\n forkXid: string\n): Promise<{ young: number; oldSum: string }> {\n const young = \"(age(t.xmin) < age($1::text::xid))\";\n const [row] = await sql.executeSql(\n `SELECT count(*) FILTER (WHERE ${young})::text AS young,\n coalesce(sum(('x' || left(md5(${keyExpr} || ':' || ${hashExpr}), 15))::bit(60)::bigint) FILTER (WHERE NOT ${young}), 0)::text AS s\n FROM ${qualified(facts.schema, facts.table)} t`,\n { database, params: [forkXid] }\n );\n return row ? { young: Number(text(row, \"young\")), oldSum: text(row, \"s\") } : { young: 0, oldSum: \"0\" };\n}\n\nasync function sampleRows(\n sql: SQLAdmin,\n database: string,\n facts: TableFacts,\n keyExpr: string,\n keys: readonly string[]\n): Promise<Map<string, Record<string, string | null>>> {\n const found = new Map<string, Record<string, string | null>>();\n if (keys.length === 0) return found;\n const rows = await sql.executeSql(\n `SELECT ${keyExpr} AS k,\n (SELECT json_object_agg(e.key, e.value) FROM json_each_text(row_to_json(t)) e)::text AS r\n FROM ${qualified(facts.schema, facts.table)} t\n WHERE ${keyExpr} = ANY($1::text[])`,\n { database, params: [[...keys]] }\n );\n for (const row of rows) found.set(text(row, \"k\"), textRecord(text(row, \"r\")));\n return found;\n}\n\nasync function firstRows(sql: SQLAdmin, database: string, facts: TableFacts, limit: number): Promise<{ key: string; row: Record<string, string | null> }[]> {\n if (limit <= 0) return [];\n const keyExpr = facts.primaryKey.length > 0 ? keyExpression(facts.primaryKey) : \"'[]'\";\n const rows = await sql.executeSql(\n `SELECT ${keyExpr} AS k,\n (SELECT json_object_agg(e.key, e.value) FROM json_each_text(row_to_json(t)) e)::text AS r\n FROM ${qualified(facts.schema, facts.table)} t\n LIMIT ${Math.floor(limit)}`,\n { database }\n );\n return rows.map((row) => ({ key: text(row, \"k\"), row: textRecord(text(row, \"r\")) }));\n}\n\nasync function countRows(sql: SQLAdmin, database: string, facts: TableFacts): Promise<number> {\n const [row] = await sql.executeSql(`SELECT count(*)::text AS n FROM ${qualified(facts.schema, facts.table)}`, { database });\n return row ? Number(text(row, \"n\")) : 0;\n}\n\nfunction emptyTableDiff(facts: TableFacts): BranchTableDataDiff {\n return {\n schema: facts.schema,\n table: facts.table,\n primaryKey: facts.primaryKey,\n inserted: 0,\n updated: 0,\n deleted: 0,\n conflicts: 0,\n targetChanges: 0,\n samples: []\n };\n}\n\nfunction positiveInteger(value: number | undefined, fallback: number, name: string, allowZero: boolean): number {\n if (value === undefined) return fallback;\n if (!Number.isInteger(value) || value < (allowZero ? 0 : 1)) {\n throw new ApiError(400, \"INVALID_INPUT\", `\\`${name}\\` must be a ${allowZero ? \"non-negative\" : \"positive\"} integer.`, undefined, EXPECTED);\n }\n return value;\n}\n\nfunction requireAdmin(admin: DatabaseAdmin | undefined): SQLAdmin & BranchAdmin {\n if (!isSQLAdmin(admin) || !isBranchAdmin(admin)) {\n throw new ApiError(501, \"BRANCH_DIFF_UNSUPPORTED\", \"This data source cannot compare branches: it needs SQL and database branching (a Postgres backend with an admin connection).\", undefined, EXPECTED);\n }\n return admin;\n}\n\nasync function computeDiff(admin: DatabaseAdmin | undefined, branchName: string, options: BranchDiffOptions = {}): Promise<ComputedDiff> {\n const sql = requireAdmin(admin);\n const sampleLimit = Math.min(positiveInteger(options.sampleLimit, DEFAULT_BRANCH_DIFF_SAMPLE_LIMIT, \"sampleLimit\", true), MAX_BRANCH_DIFF_SAMPLE_LIMIT);\n const maxRows = positiveInteger(options.maxRowsPerTable, DEFAULT_BRANCH_DIFF_MAX_ROWS, \"maxRowsPerTable\", false);\n const schemas = options.schemas && options.schemas.length > 0 ? [...new Set(options.schemas)].sort() : null;\n\n const branch = await sql.getBranchInfo(branchName);\n if (!branch) throw new ApiError(404, \"BRANCH_NOT_FOUND\", `Branch \"${branchName}\" not found.`, undefined, EXPECTED);\n const branchDb = branch.database;\n const targetDb = branch.parentDatabase;\n\n const [targetCatalog, branchCatalog, targetObjects, branchObjects, fork] = await Promise.all([\n readCatalog(sql, targetDb, schemas),\n readCatalog(sql, branchDb, schemas),\n readSchemaObjects(sql, targetDb, schemas),\n readSchemaObjects(sql, branchDb, schemas),\n branch.forkXid !== undefined\n ? sql.executeSql(\"SELECT $1::text AS xid, age($1::text::xid)::text AS age\", { database: targetDb, params: [branch.forkXid] })\n : sql.executeSql(\"SELECT xmin::text AS xid, age(xmin)::text AS age FROM pg_database WHERE datname = $1\", { database: targetDb, params: [branchDb] })\n ]);\n const forkAge = fork[0] ? Number(text(fork[0], \"age\")) : NaN;\n const forkPointKnown = Number.isFinite(forkAge) && forkAge >= 0 && forkAge < FROZEN_AGE;\n const forkXid = forkPointKnown && fork[0] ? text(fork[0], \"xid\") : null;\n\n const schemaChanges = [...diffCatalogs(targetCatalog, branchCatalog), ...diffSchemaObjects(targetObjects, branchObjects)];\n const data: BranchTableDataDiff[] = [];\n const matched = new Map<string, MatchedTable>();\n /** What the fingerprint covers, per table, beyond the schema. */\n const seen: Record<string, unknown>[] = [];\n\n // Tables on one side only: every row is the branch's insert or delete.\n const shared: { key: string; target: TableFacts; branch: TableFacts; compared: string[] }[] = [];\n for (const key of [...new Set([...targetCatalog.keys(), ...branchCatalog.keys()])].sort()) {\n const before = targetCatalog.get(key);\n const after = branchCatalog.get(key);\n if (before && after) {\n const types = new Map(before.columns.map((column) => [column.name, column.type]));\n const compared = after.columns\n .filter((column) => types.get(column.name) === column.type)\n .map((column) => column.name)\n .sort();\n shared.push({ key, target: before, branch: after, compared });\n continue;\n }\n const facts = after ?? before;\n if (!facts) continue;\n const database = after ? branchDb : targetDb;\n const count = await countRows(sql, database, facts);\n seen.push({ key, side: after ? \"branch\" : \"target\", count });\n if (count === 0) continue;\n const entry = emptyTableDiff(facts);\n const change = after ? \"inserted\" : \"deleted\";\n if (after) entry.inserted = count;\n else entry.deleted = count;\n entry.samples = (await firstRows(sql, database, facts, sampleLimit)).map(({ key: rowKey, row }) =>\n after ? { change, key: rowKey, after: redacted(row) } : { change, key: rowKey, before: redacted(row) });\n entry.blocked = after\n ? { code: \"TABLE_ADDED\", message: \"The table is new on the branch. Create it on the target with your migrations (rebase db push), then merge its rows.\" }\n : { code: \"TABLE_DROPPED\", message: \"The branch dropped the table. A merge never drops one; drop it with your migrations.\" };\n data.push(entry);\n }\n\n // Tables on both: a checksum each, and rows matched only where it differs.\n const probes = shared.map(({ target, compared }) => ({\n facts: target,\n keyExpr: target.primaryKey.length > 0 ? keyExpression(target.primaryKey) : \"''\",\n hashExpr: hashExpression(compared.length > 0\n ? target.columns.filter((column) => compared.includes(column.name)).sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))\n : target.columns)\n }));\n const [targetSums, branchSums] = await Promise.all([\n checksums(sql, targetDb, probes),\n checksums(sql, branchDb, probes)\n ]);\n\n for (let index = 0; index < shared.length; index += 1) {\n const { key, target, branch: branchFacts, compared } = shared[index];\n const { keyExpr, hashExpr } = probes[index];\n const targetSum = targetSums[index];\n const branchSum = branchSums[index];\n if (targetSum.count === branchSum.count && targetSum.sum === branchSum.sum) continue;\n\n const entry = emptyTableDiff(branchFacts);\n const rowCounts = { target: targetSum.count, branch: branchSum.count };\n const typesOf = (facts: TableFacts) => facts.primaryKey.map((column) => facts.columns.find((c) => c.name === column)?.type ?? \"\");\n let block: BranchMergeBlock | undefined;\n if (!sameList(target.primaryKey, branchFacts.primaryKey) || !sameList(typesOf(target), typesOf(branchFacts))) {\n block = { code: \"PRIMARY_KEY_DIFFERS\", message: \"The primary key differs between the branch and the target, so rows cannot be matched.\" };\n } else if (target.primaryKey.length === 0) {\n block = { code: \"NO_PRIMARY_KEY\", message: \"The table has no primary key, so its rows cannot be matched one by one.\" };\n } else if (targetSum.count > maxRows || branchSum.count > maxRows) {\n block = { code: \"TOO_LARGE\", message: `The table holds more than ${maxRows} rows on one side, more than a diff matches row by row.` };\n }\n if (block) {\n // Rows that cannot be matched can still be told untouched by the\n // branch: it wrote none since the fork, and the rows from before\n // it are the same on both sides. Then every difference is the\n // target's, and there is nothing to merge or to block.\n if (forkXid !== null) {\n const [targetSplit, branchSplit] = await Promise.all([\n forkSplit(sql, targetDb, target, keyExpr, hashExpr, forkXid),\n forkSplit(sql, branchDb, branchFacts, keyExpr, hashExpr, forkXid)\n ]);\n if (branchSplit.young === 0 && targetSplit.oldSum === branchSplit.oldSum) {\n entry.targetChanges = targetSplit.young;\n seen.push({ key, untouched: true, targetSum, branchSum });\n data.push(entry);\n continue;\n }\n }\n // Which rows changed, and on which side, is unknown: the counts\n // stay 0, `rowCounts` says what each side holds, and a merge\n // refuses the table unless told to skip it. A count difference\n // is not the branch's inserts or deletes — the target may have\n // made it.\n entry.blocked = block;\n entry.rowCounts = rowCounts;\n seen.push({ key, block: block.code, targetSum, branchSum });\n data.push(entry);\n continue;\n }\n\n const [targetRows, branchRows] = await Promise.all([\n rowStates(sql, targetDb, target, keyExpr, hashExpr, forkXid),\n rowStates(sql, branchDb, branchFacts, keyExpr, hashExpr, forkXid)\n ]);\n const rows = classifyRows(targetRows, branchRows, forkPointKnown);\n\n // A table the target rewrote reads as all the target's: the branch's\n // deletes would be kept and its updates become conflicts. One the\n // branch rewrote misreads only what the target changed, as conflicts.\n // Either way it is blocked, never merged on a guess.\n const rewritten = forkPointKnown ? rewrittenSides(targetRows, branchRows) : [];\n if (rewritten.includes(\"target\") || (rewritten.includes(\"branch\") && rows.conflicts.length > 0)) {\n const who = rewritten.includes(\"target\") ? targetDb : \"The branch\";\n entry.blocked = {\n code: \"TABLE_REWRITTEN\",\n message: `${who} rewrote the table after the branch was made — a column type change, a volatile DEFAULT, VACUUM FULL or ` +\n \"CLUSTER writes every row anew — so every row reads as written since, and which side changed which row can no longer \" +\n `be told. Merge the branch before such a migration, or make the branch again from ${targetDb}.`\n };\n entry.rowCounts = rowCounts;\n seen.push({ key, block: \"TABLE_REWRITTEN\", targetSum, branchSum });\n data.push(entry);\n continue;\n }\n // Without a fork point every target-only row is already the branch's\n // delete, and nothing can be told the target's.\n const referenced = forkXid !== null\n ? await deleteConflicts(sql, targetDb, forkXid, target, keyExpr, rows.deleted)\n : { keys: [], referencedBy: [] };\n if (referenced.keys.length > 0) {\n const inConflict = new Set(referenced.keys);\n rows.deleted = rows.deleted.filter((rowKey) => !inConflict.has(rowKey));\n entry.referencedBy = referenced.referencedBy.map(({ key: _key, ...rest }) => rest);\n }\n entry.inserted = rows.inserted.length;\n entry.updated = rows.updated.length;\n entry.deleted = rows.deleted.length;\n entry.conflicts = rows.conflicts.length + referenced.keys.length;\n entry.targetChanges = rows.targetChanges;\n const branchChanged = entry.inserted + entry.updated + entry.deleted + entry.conflicts > 0;\n if (!branchChanged && entry.targetChanges === 0) continue;\n\n const columnsDiffer = target.columns.length !== compared.length || branchFacts.columns.length !== compared.length;\n if (branchChanged && columnsDiffer) {\n entry.blocked = {\n code: \"COLUMNS_DIFFER\",\n message: \"The table's columns differ between the branch and the target, so its rows are compared on the columns they \" +\n \"share and cannot be merged. Add the missing columns to the target with your migrations (rebase db push) and merge \" +\n \"again. A migration that rewrites the table — a column type change does — makes every row read as written since \" +\n \"and blocks the table for good: merge before such a change, or make the branch again after it.\"\n };\n }\n\n const [conflictKeys, insertedKeys, updatedKeys, deletedKeys] = pickSamples(\n [[...rows.conflicts, ...referenced.keys], rows.inserted, rows.updated, rows.deleted], sampleLimit);\n const [before, after] = await Promise.all([\n sampleRows(sql, targetDb, target, keyExpr, [...conflictKeys, ...updatedKeys, ...deletedKeys]),\n sampleRows(sql, branchDb, branchFacts, keyExpr, [...conflictKeys, ...insertedKeys, ...updatedKeys])\n ]);\n const changed = (rowKey: string): string[] => {\n const old = before.get(rowKey) ?? {};\n const now = after.get(rowKey) ?? {};\n return compared.filter((column) => old[column] !== now[column]);\n };\n // Which columns changed is read from the values; what is shown of\n // them leaves secrets out.\n const shownBefore = (rowKey: string) => redacted(before.get(rowKey));\n const shownAfter = (rowKey: string) => redacted(after.get(rowKey));\n const samples: BranchRowChange[] = [\n ...conflictKeys.map((rowKey): BranchRowChange => after.has(rowKey)\n ? { change: \"conflict\", key: rowKey, before: shownBefore(rowKey), after: shownAfter(rowKey), changedColumns: changed(rowKey) }\n // Deleted on the branch, and referenced by the target's rows.\n : { change: \"conflict\", key: rowKey, before: shownBefore(rowKey) }),\n ...insertedKeys.map((rowKey): BranchRowChange => ({ change: \"inserted\", key: rowKey, after: shownAfter(rowKey) })),\n ...updatedKeys.map((rowKey): BranchRowChange => ({ change: \"updated\", key: rowKey, before: shownBefore(rowKey), after: shownAfter(rowKey), changedColumns: changed(rowKey) })),\n ...deletedKeys.map((rowKey): BranchRowChange => ({ change: \"deleted\", key: rowKey, before: shownBefore(rowKey) }))\n ];\n entry.samples = samples;\n data.push(entry);\n\n const targetDigest = md5(targetRows.map((row) => `${row.key}:${row.hash}`).join(\",\"));\n const stateDigest = (states: readonly RowState[]) => md5(states.map((row) => `${row.key}:${row.hash}:${row.young ? 1 : 0}`).join(\",\"));\n seen.push({ key, target: stateDigest(targetRows), branch: stateDigest(branchRows), referencedBy: entry.referencedBy ?? [] });\n matched.set(key, {\n facts: target,\n keyExpression: keyExpr,\n hashExpression: hashExpr,\n targetDigest,\n branchHashes: new Map(branchRows.map((row) => [row.key, row.hash])),\n rows,\n deleteConflicts: referenced\n });\n }\n\n const fingerprint = createHash(\"sha256\")\n .update(JSON.stringify({ branchDb, targetDb, schemas, maxRows, forkPointKnown, schema: schemaChanges, tables: seen }), \"utf8\")\n .digest(\"hex\");\n\n return {\n diff: {\n branch: branch.name,\n branchDatabase: branchDb,\n targetDatabase: targetDb,\n schemas: schemas ?? [...new Set([...targetCatalog.values(), ...branchCatalog.values()].map((facts) => facts.schema))].sort(),\n forkPointKnown,\n fingerprint,\n schema: schemaChanges,\n data,\n sampleLimit,\n computedAt: new Date().toISOString()\n },\n matched,\n forkXid\n };\n}\n\n/**\n * What the branch `branchName` changed against the database it was made from:\n * its schema and policies, and per table the rows it inserted, updated and\n * deleted, with up to `sampleLimit` of them. Reads both databases and writes\n * neither.\n */\nexport async function diffDatabaseBranch(\n admin: DatabaseAdmin | undefined,\n branchName: string,\n options?: BranchDiffOptions\n): Promise<BranchDiff> {\n return (await computeDiff(admin, branchName, options)).diff;\n}\n\n// ── Merge ────────────────────────────────────────────────────────────────────\n\n/** Up to ten keys, for a message. */\nfunction keyList(keys: readonly string[]): string {\n return keys.length > 10 ? `${keys.slice(0, 10).join(\", \")} and ${keys.length - 10} more` : keys.join(\", \");\n}\n\n/** `public.posts (1 row, ON DELETE CASCADE)`, for each foreign key. */\nfunction referencingList(found: readonly BranchReferencingRows[]): string {\n return found.map((entry) => `${entry.schema}.${entry.table} (${entry.rows} row(s), ON DELETE ${entry.onDelete.toUpperCase()})`).join(\", \");\n}\n\n/** What Postgres said about a failed statement, found on the error or anywhere down its `cause`. */\ninterface DatabaseFailure {\n sqlState: string;\n message: string;\n table?: string;\n constraint?: string;\n detail?: string;\n}\n\nfunction databaseFailure(error: unknown): DatabaseFailure | undefined {\n const field = (value: object, key: string): string | undefined => {\n const found: unknown = Reflect.get(value, key);\n return typeof found === \"string\" && found !== \"\" ? found : undefined;\n };\n let current: unknown = error;\n for (let depth = 0; depth < 5 && typeof current === \"object\" && current !== null; depth += 1) {\n const sqlState = field(current, \"code\");\n if (sqlState && /^[0-9A-Z]{5}$/.test(sqlState)) {\n return {\n sqlState,\n message: field(current, \"message\") ?? sqlState,\n table: field(current, \"table\"),\n constraint: field(current, \"constraint\"),\n detail: field(current, \"detail\")\n };\n }\n current = Reflect.get(current, \"cause\");\n }\n return undefined;\n}\n\n/** The marker a merge statement's own refusal carries, to be told from any other failure. */\nconst MERGE_REFUSAL = \"rebase branch merge refused\";\n\n/**\n * Apply the rows the branch `branchName` changed to the database it was made\n * from. See `BranchMergeOptions` for what wins and when nothing is written.\n */\nexport async function mergeDatabaseBranch(\n admin: DatabaseAdmin | undefined,\n branchName: string,\n options: BranchMergeOptions\n): Promise<BranchMergeResult> {\n const sql = requireAdmin(admin);\n if (typeof options?.expectedFingerprint !== \"string\" || options.expectedFingerprint === \"\") {\n throw new ApiError(400, \"INVALID_INPUT\", \"Send `expectedFingerprint`: the fingerprint of the diff that was reviewed.\", undefined, EXPECTED);\n }\n const onConflict = options.onConflict ?? \"abort\";\n if (onConflict !== \"abort\" && onConflict !== \"branch\" && onConflict !== \"target\") {\n throw new ApiError(400, \"INVALID_INPUT\", \"`onConflict` must be \\\"abort\\\", \\\"branch\\\" or \\\"target\\\".\", undefined, EXPECTED);\n }\n\n const { diff, matched, forkXid } = await computeDiff(admin, branchName, {\n sampleLimit: 0,\n schemas: options.schemas,\n maxRowsPerTable: options.maxRowsPerTable\n });\n if (diff.fingerprint !== options.expectedFingerprint) {\n throw new ApiError(409, \"BRANCH_DIFF_STALE\", `The branch or ${diff.targetDatabase} changed since the diff was read. Nothing was merged; review the diff again.`, undefined, EXPECTED);\n }\n\n // Every blocked table: one whose rows could not be matched differs in\n // ways nobody counted, and a zero there means \"unknown\", not \"unchanged\".\n const blocked = diff.data.filter((entry) => entry.blocked);\n if (blocked.length > 0 && options.skipBlocked !== true) {\n throw new ApiError(409, \"BRANCH_MERGE_BLOCKED\", `${blocked.length} table(s) with changed rows cannot be merged ` +\n `(${blocked.map((entry) => `${entry.schema}.${entry.table}: ${entry.blocked?.code}`).join(\", \")}). Nothing was merged. ` +\n \"Merge with skipBlocked to leave them as they are.\",\n { tables: blocked.map((entry): RefusedTable => ({ schema: entry.schema, table: entry.table, code: entry.blocked?.code, reason: entry.blocked?.message ?? \"\" })) },\n EXPECTED\n );\n }\n const conflicted = diff.data.filter((entry) => !entry.blocked && entry.conflicts > 0);\n if (conflicted.length > 0 && onConflict === \"abort\") {\n const referenced = conflicted.flatMap((entry) => {\n const table = matched.get(tableKey(entry.schema, entry.table));\n return table && table.deleteConflicts.keys.length > 0 ? [{ entry, conflicts: table.deleteConflicts }] : [];\n });\n const both = conflicted.reduce((sum, entry) => sum + entry.conflicts, 0) -\n referenced.reduce((sum, { conflicts }) => sum + conflicts.keys.length, 0);\n const sentences: string[] = [];\n if (both > 0) sentences.push(`${both} row(s) were changed on both the branch and ${diff.targetDatabase}.`);\n for (const { entry, conflicts } of referenced) {\n sentences.push(`The branch deleted ${conflicts.keys.length} row(s) of ${entry.schema}.${entry.table} (${keyList(conflicts.keys)}) that rows ` +\n `${diff.targetDatabase} wrote since still reference: ${referencingList(conflicts.referencedBy)}.`);\n }\n const remedy = referenced.length > 0\n ? \"Merge again with onConflict \\\"target\\\" to keep those rows, or \\\"branch\\\" to delete them and let the foreign keys reach the rows that reference them.\"\n : \"Merge again choosing which side wins.\";\n throw new ApiError(409, \"BRANCH_MERGE_CONFLICT\", `${sentences.join(\" \")} Nothing was merged. ${remedy}`,\n {\n tables: conflicted.map((entry): RefusedTable => {\n const keys = matched.get(tableKey(entry.schema, entry.table))?.deleteConflicts;\n const reason = keys && keys.keys.length > 0\n ? `${entry.conflicts} conflicting row(s); deleted on the branch and referenced by rows ${diff.targetDatabase} wrote since: ` +\n `${keyList(keys.keys)} (${referencingList(keys.referencedBy)})`\n : `${entry.conflicts} conflicting row(s)`;\n return { schema: entry.schema, table: entry.table, reason };\n })\n },\n EXPECTED\n );\n }\n\n const result: BranchMergeResult = {\n branch: diff.branch,\n targetDatabase: diff.targetDatabase,\n tables: [],\n skipped: blocked.flatMap((entry) => entry.blocked ? [{ schema: entry.schema, table: entry.table, reason: entry.blocked }] : []),\n conflictsKept: onConflict === \"target\" ? conflicted.reduce((sum, entry) => sum + entry.conflicts, 0) : 0,\n schemaChangesNotMerged: diff.schema.length,\n warnings: []\n };\n\n // What to write, table by table, with the branch's rows read once more and\n // checked against the hashes the diff saw.\n const params: unknown[] = [];\n const param = (value: unknown): string => {\n params.push(value);\n return `$${params.length}`;\n };\n const guards: string[] = [];\n const writes: { name: string; sql: string; expected: number }[] = [];\n const written: { entry: BranchTableDataDiff; table: MatchedTable; inserted: number; updated: number; deleted: number }[] = [];\n\n for (const entry of diff.data) {\n if (entry.blocked) continue;\n const table = matched.get(tableKey(entry.schema, entry.table));\n if (!table) continue;\n const updates = onConflict === \"branch\" ? [...table.rows.updated, ...table.rows.conflicts] : table.rows.updated;\n const inserts = table.rows.inserted;\n const deletes = onConflict === \"branch\" ? [...table.rows.deleted, ...table.deleteConflicts.keys] : table.rows.deleted;\n if (updates.length + inserts.length + deletes.length === 0) continue;\n\n const branchRows = await sql.executeSql(\n `SELECT ${table.keyExpression} AS k, ${table.hashExpression} AS h, row_to_json(t)::text AS r\n FROM ${qualified(table.facts.schema, table.facts.table)} t\n WHERE ${table.keyExpression} = ANY($1::text[])`,\n { database: diff.branchDatabase, params: [[...updates, ...inserts]] }\n );\n const rowText = new Map<string, string>();\n for (const row of branchRows) {\n const rowKey = text(row, \"k\");\n if (table.branchHashes.get(rowKey) !== text(row, \"h\")) break;\n rowText.set(rowKey, text(row, \"r\"));\n }\n if (rowText.size !== updates.length + inserts.length) {\n throw new ApiError(409, \"BRANCH_DIFF_STALE\", \"The branch changed while it was being merged. Nothing was merged; review the diff again.\", undefined, EXPECTED);\n }\n const rowsJson = (keys: readonly string[]) => `[${keys.map((rowKey) => rowText.get(rowKey)).join(\",\")}]`;\n\n const target = qualified(table.facts.schema, table.facts.table);\n const index = written.length;\n const writesBefore = writes.length;\n const writable = table.facts.columns.filter((column) => column.generated !== \"s\");\n const primaryKey = new Set(table.facts.primaryKey);\n const keyMatch = table.facts.primaryKey.map((column) => `t.${ident(column)} = r.${ident(column)}`).join(\" AND \");\n if (deletes.length > 0) {\n writes.push({\n name: `d${index}`,\n sql: `DELETE FROM ${target} AS t WHERE (SELECT ok FROM guard) AND ${table.keyExpression} = ANY(${param([...deletes])}::text[]) RETURNING 1`,\n expected: deletes.length\n });\n }\n const settable = writable.filter((column) => !primaryKey.has(column.name) && column.identity !== \"a\");\n if (updates.length > 0 && settable.length > 0) {\n writes.push({\n name: `u${index}`,\n sql: `UPDATE ${target} AS t SET (${settable.map((column) => ident(column.name)).join(\", \")}) = ` +\n `ROW(${settable.map((column) => `r.${ident(column.name)}`).join(\", \")}) ` +\n `FROM json_populate_recordset(NULL::${target}, ${param(rowsJson(updates))}::json) AS r ` +\n `WHERE (SELECT ok FROM guard) AND ${keyMatch} RETURNING 1`,\n expected: updates.length\n });\n }\n if (inserts.length > 0) {\n writes.push({\n name: `i${index}`,\n sql: `INSERT INTO ${target} (${writable.map((column) => ident(column.name)).join(\", \")}) OVERRIDING SYSTEM VALUE ` +\n `SELECT ${writable.map((column) => `r.${ident(column.name)}`).join(\", \")} ` +\n `FROM json_populate_recordset(NULL::${target}, ${param(rowsJson(inserts))}::json) AS r ` +\n `WHERE (SELECT ok FROM guard) RETURNING 1`,\n expected: inserts.length\n });\n }\n if (writes.length === writesBefore) continue;\n guards.push(\n `(SELECT md5(coalesce(string_agg(${table.keyExpression} || ':' || ${table.hashExpression}, ',' ORDER BY (${table.keyExpression}) COLLATE \"C\"), '')) ` +\n `FROM ${target} t) = ${param(table.targetDigest)}`\n );\n // The target's rows that reference what this deletes, as many as the\n // diff counted: none for a plain delete, the ones it named for a\n // conflict the branch won. A row written since would be reached by a\n // delete nobody reviewed.\n if (deletes.length > 0 && forkXid !== null) {\n const counted = new Map(table.deleteConflicts.referencedBy.map((found) => [found.constraint + \"\\u0000\" + found.schema + \".\" + found.table, found.rows]));\n const keys = await referencingKeys(sql, diff.targetDatabase, table.facts);\n const keysParam = keys.length > 0 ? param([...deletes]) : \"\";\n const forkParam = keys.length > 0 ? param(forkXid) : \"\";\n for (const key of keys) {\n const expected = onConflict === \"branch\" ? counted.get(key.constraint + \"\\u0000\" + key.schema + \".\" + key.table) ?? 0 : 0;\n guards.push(`(SELECT count(*) ${referencingRowsFrom(table.facts, table.keyExpression, key, keysParam, forkParam)}) = ${expected}`);\n }\n }\n written.push({ entry, table, inserted: inserts.length, updated: updates.length, deleted: deletes.length });\n }\n\n if (written.length === 0) return result;\n\n if (onConflict === \"branch\") {\n for (const { entry, table } of written) {\n const { keys, referencedBy } = table.deleteConflicts;\n if (keys.length === 0) continue;\n result.warnings.push(\n `Deleting ${keys.length} row(s) of ${entry.schema}.${entry.table} the branch deleted (${keyList(keys)}) reached rows ` +\n `${diff.targetDatabase} wrote since the branch was made: ${referencingList(referencedBy)}.`\n );\n }\n }\n\n // One statement: the guard, every write, and a check that each wrote what\n // it was meant to. Non-deferrable foreign keys are checked at its end, so\n // the order of the tables does not matter; a failure anywhere — a\n // constraint, a trigger, a policy that hid a row — rolls back all of it.\n const counted = writes.map((write) => `(SELECT count(*) FROM ${write.name}) = ${write.expected}`).join(\" AND \");\n const statement =\n `WITH guard AS (SELECT (${guards.join(\" AND \")}) AS ok),\\n` +\n writes.map((write) => `${write.name} AS (${write.sql})`).join(\",\\n\") +\n `\\nSELECT CASE WHEN NOT (SELECT ok FROM guard) THEN 'conflict'\n WHEN ${counted || \"true\"} THEN 'merged'\n ELSE CAST('${MERGE_REFUSAL}: a write did not reach the rows it read (' ||\n ${writes.map((write) => `(SELECT count(*) FROM ${write.name})::text`).join(\" || ',' || \") || \"''\"} || ')' AS integer)::text\n END AS outcome`;\n let outcome: string;\n try {\n const [row] = await sql.executeSql(statement, { database: diff.targetDatabase, params });\n outcome = row ? text(row, \"outcome\") : \"\";\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n if (message.includes(MERGE_REFUSAL)) {\n throw new ApiError(409, \"BRANCH_MERGE_CONFLICT\", `${diff.targetDatabase} refused part of the merge — a row changed meanwhile, a trigger skipped it, or a policy hid it. Nothing was merged.`, undefined, EXPECTED);\n }\n // The target refused the branch's rows: a constraint, an enum label,\n // a foreign key it holds and the branch did not. The rows are the\n // two databases disagreeing, not a request error — left to the\n // generic mapping, an enum label read as a bad filter value (400).\n const failure = databaseFailure(error);\n if (failure && /^(22|23|P0|42501)/.test(failure.sqlState)) {\n const where = [failure.table ? `table ${failure.table}` : \"\", failure.constraint ? `constraint ${failure.constraint}` : \"\"].filter(Boolean).join(\", \");\n throw new ApiError(409, \"BRANCH_MERGE_FAILED\",\n `${diff.targetDatabase} refused the branch's rows${where ? ` (${where})` : \"\"}: ${failure.message}. Nothing was merged. ` +\n \"The branch's rows break a rule the target holds — its schema or constraints differ from the branch's; see the diff's schema changes.\",\n { sqlState: failure.sqlState, table: failure.table, constraint: failure.constraint, detail: failure.detail },\n EXPECTED);\n }\n throw error;\n }\n if (outcome !== \"merged\") {\n throw new ApiError(409, \"BRANCH_MERGE_CONFLICT\", `${diff.targetDatabase} changed while the merge was being prepared. Nothing was merged; review the diff again.`, undefined, EXPECTED);\n }\n\n for (const { entry, table, inserted, updated, deleted } of written) {\n result.tables.push({ schema: entry.schema, table: entry.table, inserted, updated, deleted });\n if (inserted === 0) continue;\n // Rows inserted with their own ids leave the sequence behind them,\n // and the target's next insert would collide with one.\n for (const column of table.facts.columns) {\n if (!column.sequence) continue;\n try {\n await sql.executeSql(\n `SELECT setval($1::regclass, m.mx)\n FROM (SELECT max(${ident(column.name)})::bigint AS mx FROM ${qualified(entry.schema, entry.table)}) m\n WHERE m.mx IS NOT NULL AND m.mx > coalesce(pg_sequence_last_value($1::regclass), 0)`,\n { database: diff.targetDatabase, params: [column.sequence] }\n );\n } catch (error: unknown) {\n result.warnings.push(\n `The rows were merged, but the sequence ${column.sequence} could not be advanced past them: ` +\n `${error instanceof Error ? error.message : String(error)}. Run SELECT setval('${column.sequence}', ` +\n `(SELECT max(${ident(column.name)}) FROM ${qualified(entry.schema, entry.table)})); on ${diff.targetDatabase}.`\n );\n }\n }\n }\n return result;\n}\n"],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ import { createRequire as __createRequire } from "module";
2
+ __createRequire(import.meta.url);
3
+ //#region src/branching/branch-diff-report.ts
4
+ /**
5
+ * `rebase db branch diff` without `--json`: what the branch changed, a line a
6
+ * change, plain text — the CLI colours nothing it would have to strip for a
7
+ * pipe.
8
+ */
9
+ function branchDiffReport(diff) {
10
+ const lines = [];
11
+ lines.push(`Branch "${diff.branch}" (${diff.branchDatabase}) against ${diff.targetDatabase}`);
12
+ if (!diff.forkPointKnown) lines.push(" ! The point the branch was made could not be read: every row that differs is counted as the branch's change.");
13
+ lines.push("");
14
+ lines.push(diff.schema.length === 0 ? "Schema and policies: no changes" : `Schema and policies: ${diff.schema.length} change(s)`);
15
+ for (const change of diff.schema) {
16
+ const sign = change.change === "added" ? "+" : change.change === "dropped" ? "-" : "~";
17
+ const what = change.name ? `${change.object} ${change.schema}.${change.table}.${change.name}` : `${change.object} ${change.schema}.${change.table}`;
18
+ lines.push(` ${sign} ${what}`);
19
+ }
20
+ lines.push("");
21
+ lines.push(diff.data.length === 0 ? "Data: no changes" : `Data: ${diff.data.length} table(s) with changed rows`);
22
+ const reached = /* @__PURE__ */ new Map();
23
+ for (const found of diff.data.flatMap((table) => table.referencedBy ?? [])) reached.set(`${found.schema}.${found.table}`, (reached.get(`${found.schema}.${found.table}`) ?? 0) + found.rows);
24
+ for (const table of diff.data) {
25
+ const name = table.collection ? `${table.schema}.${table.table} (${table.collection})` : `${table.schema}.${table.table}`;
26
+ const counts = table.rowCounts ? [`rows: ${table.rowCounts.target} on ${diff.targetDatabase}, ${table.rowCounts.branch} on the branch, not matched one by one`] : [
27
+ `+${table.inserted}`,
28
+ `~${table.updated}`,
29
+ `-${table.deleted}`
30
+ ];
31
+ if (table.conflicts > 0) counts.push(`${table.conflicts} conflict(s)`);
32
+ const referencing = Math.min(reached.get(`${table.schema}.${table.table}`) ?? 0, table.targetChanges);
33
+ if (table.targetChanges > 0) counts.push(referencing > 0 ? `${table.targetChanges} changed on ${diff.targetDatabase} only (${referencing} reference a row the branch deleted)` : `${table.targetChanges} changed on ${diff.targetDatabase} only, kept`);
34
+ lines.push(` ${name}: ${counts.join(" ")}`);
35
+ if (table.referencedBy && table.referencedBy.length > 0) {
36
+ const tables = table.referencedBy.map((found) => `${found.schema}.${found.table} (${found.rows} row(s), ON DELETE ${found.onDelete.toUpperCase()})`);
37
+ lines.push(` deleted on the branch, but referenced by rows ${diff.targetDatabase} wrote since: ${tables.join(", ")}`);
38
+ }
39
+ if (table.blocked) lines.push(` cannot be merged: ${table.blocked.message}`);
40
+ }
41
+ lines.push("");
42
+ lines.push(`Fingerprint: ${diff.fingerprint}`);
43
+ return lines;
44
+ }
45
+ //#endregion
46
+ export { branchDiffReport };
47
+
48
+ //# sourceMappingURL=branch-diff-report-DpvVf7hR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"branch-diff-report-DpvVf7hR.js","names":[],"sources":["../src/branching/branch-diff-report.ts"],"sourcesContent":["import type { BranchDiff } from \"@rebasepro/types\";\n\n/**\n * `rebase db branch diff` without `--json`: what the branch changed, a line a\n * change, plain text — the CLI colours nothing it would have to strip for a\n * pipe.\n */\nexport function branchDiffReport(diff: BranchDiff): string[] {\n const lines: string[] = [];\n lines.push(`Branch \"${diff.branch}\" (${diff.branchDatabase}) against ${diff.targetDatabase}`);\n if (!diff.forkPointKnown) {\n lines.push(\" ! The point the branch was made could not be read: every row that differs is counted as the branch's change.\");\n }\n lines.push(\"\");\n lines.push(diff.schema.length === 0 ? \"Schema and policies: no changes\" : `Schema and policies: ${diff.schema.length} change(s)`);\n for (const change of diff.schema) {\n const sign = change.change === \"added\" ? \"+\" : change.change === \"dropped\" ? \"-\" : \"~\";\n const what = change.name ? `${change.object} ${change.schema}.${change.table}.${change.name}` : `${change.object} ${change.schema}.${change.table}`;\n lines.push(` ${sign} ${what}`);\n }\n lines.push(\"\");\n lines.push(diff.data.length === 0 ? \"Data: no changes\" : `Data: ${diff.data.length} table(s) with changed rows`);\n // The target's rows, per table, that reference a row the branch deleted:\n // not \"kept\" — a merge the branch wins reaches them.\n const reached = new Map<string, number>();\n for (const found of diff.data.flatMap((table) => table.referencedBy ?? [])) {\n reached.set(`${found.schema}.${found.table}`, (reached.get(`${found.schema}.${found.table}`) ?? 0) + found.rows);\n }\n for (const table of diff.data) {\n const name = table.collection ? `${table.schema}.${table.table} (${table.collection})` : `${table.schema}.${table.table}`;\n // A table whose rows were not matched has counts nobody knows.\n const counts = table.rowCounts\n ? [`rows: ${table.rowCounts.target} on ${diff.targetDatabase}, ${table.rowCounts.branch} on the branch, not matched one by one`]\n : [`+${table.inserted}`, `~${table.updated}`, `-${table.deleted}`];\n if (table.conflicts > 0) counts.push(`${table.conflicts} conflict(s)`);\n const referencing = Math.min(reached.get(`${table.schema}.${table.table}`) ?? 0, table.targetChanges);\n if (table.targetChanges > 0) {\n counts.push(referencing > 0\n ? `${table.targetChanges} changed on ${diff.targetDatabase} only (${referencing} reference a row the branch deleted)`\n : `${table.targetChanges} changed on ${diff.targetDatabase} only, kept`);\n }\n lines.push(` ${name}: ${counts.join(\" \")}`);\n if (table.referencedBy && table.referencedBy.length > 0) {\n const tables = table.referencedBy.map((found) => `${found.schema}.${found.table} (${found.rows} row(s), ON DELETE ${found.onDelete.toUpperCase()})`);\n lines.push(` deleted on the branch, but referenced by rows ${diff.targetDatabase} wrote since: ${tables.join(\", \")}`);\n }\n if (table.blocked) lines.push(` cannot be merged: ${table.blocked.message}`);\n }\n lines.push(\"\");\n lines.push(`Fingerprint: ${diff.fingerprint}`);\n return lines;\n}\n"],"mappings":";;;;;;;;AAOA,SAAgB,iBAAiB,MAA4B;CACzD,MAAM,QAAkB,CAAC;CACzB,MAAM,KAAK,WAAW,KAAK,OAAO,KAAK,KAAK,eAAe,YAAY,KAAK,gBAAgB;CAC5F,IAAI,CAAC,KAAK,gBACN,MAAM,KAAK,gHAAgH;CAE/H,MAAM,KAAK,EAAE;CACb,MAAM,KAAK,KAAK,OAAO,WAAW,IAAI,oCAAoC,wBAAwB,KAAK,OAAO,OAAO,WAAW;CAChI,KAAK,MAAM,UAAU,KAAK,QAAQ;EAC9B,MAAM,OAAO,OAAO,WAAW,UAAU,MAAM,OAAO,WAAW,YAAY,MAAM;EACnF,MAAM,OAAO,OAAO,OAAO,GAAG,OAAO,OAAO,GAAG,OAAO,OAAO,GAAG,OAAO,MAAM,GAAG,OAAO,SAAS,GAAG,OAAO,OAAO,GAAG,OAAO,OAAO,GAAG,OAAO;EAC5I,MAAM,KAAK,KAAK,KAAK,GAAG,MAAM;CAClC;CACA,MAAM,KAAK,EAAE;CACb,MAAM,KAAK,KAAK,KAAK,WAAW,IAAI,qBAAqB,SAAS,KAAK,KAAK,OAAO,4BAA4B;CAG/G,MAAM,0BAAU,IAAI,IAAoB;CACxC,KAAK,MAAM,SAAS,KAAK,KAAK,SAAS,UAAU,MAAM,gBAAgB,CAAC,CAAC,GACrE,QAAQ,IAAI,GAAG,MAAM,OAAO,GAAG,MAAM,UAAU,QAAQ,IAAI,GAAG,MAAM,OAAO,GAAG,MAAM,OAAO,KAAK,KAAK,MAAM,IAAI;CAEnH,KAAK,MAAM,SAAS,KAAK,MAAM;EAC3B,MAAM,OAAO,MAAM,aAAa,GAAG,MAAM,OAAO,GAAG,MAAM,MAAM,IAAI,MAAM,WAAW,KAAK,GAAG,MAAM,OAAO,GAAG,MAAM;EAElH,MAAM,SAAS,MAAM,YACf,CAAC,SAAS,MAAM,UAAU,OAAO,MAAM,KAAK,eAAe,IAAI,MAAM,UAAU,OAAO,uCAAuC,IAC7H;GAAC,IAAI,MAAM;GAAY,IAAI,MAAM;GAAW,IAAI,MAAM;EAAS;EACrE,IAAI,MAAM,YAAY,GAAG,OAAO,KAAK,GAAG,MAAM,UAAU,aAAa;EACrE,MAAM,cAAc,KAAK,IAAI,QAAQ,IAAI,GAAG,MAAM,OAAO,GAAG,MAAM,OAAO,KAAK,GAAG,MAAM,aAAa;EACpG,IAAI,MAAM,gBAAgB,GACtB,OAAO,KAAK,cAAc,IACpB,GAAG,MAAM,cAAc,cAAc,KAAK,eAAe,SAAS,YAAY,wCAC9E,GAAG,MAAM,cAAc,cAAc,KAAK,eAAe,YAAY;EAE/E,MAAM,KAAK,KAAK,KAAK,IAAI,OAAO,KAAK,IAAI,GAAG;EAC5C,IAAI,MAAM,gBAAgB,MAAM,aAAa,SAAS,GAAG;GACrD,MAAM,SAAS,MAAM,aAAa,KAAK,UAAU,GAAG,MAAM,OAAO,GAAG,MAAM,MAAM,IAAI,MAAM,KAAK,qBAAqB,MAAM,SAAS,YAAY,EAAE,EAAE;GACnJ,MAAM,KAAK,qDAAqD,KAAK,eAAe,gBAAgB,OAAO,KAAK,IAAI,GAAG;EAC3H;EACA,IAAI,MAAM,SAAS,MAAM,KAAK,yBAAyB,MAAM,QAAQ,SAAS;CAClF;CACA,MAAM,KAAK,EAAE;CACb,MAAM,KAAK,gBAAgB,KAAK,aAAa;CAC7C,OAAO;AACX"}
@@ -0,0 +1,7 @@
1
+ import type { BranchDiff } from "@rebasepro/types";
2
+ /**
3
+ * `rebase db branch diff` without `--json`: what the branch changed, a line a
4
+ * change, plain text — the CLI colours nothing it would have to strip for a
5
+ * pipe.
6
+ */
7
+ export declare function branchDiffReport(diff: BranchDiff): string[];
@@ -0,0 +1,115 @@
1
+ import { type BranchDiff, type BranchDiffOptions, type BranchMergeOptions, type BranchMergeResult, type BranchSchemaChange, type DatabaseAdmin } from "@rebasepro/types";
2
+ /** Rows shown per table when the caller does not say. */
3
+ export declare const DEFAULT_BRANCH_DIFF_SAMPLE_LIMIT = 100;
4
+ /** The most rows a diff samples per table, whatever the caller asks for. */
5
+ export declare const MAX_BRANCH_DIFF_SAMPLE_LIMIT = 1000;
6
+ /** What a sample shows in place of a secret's value. */
7
+ export declare const REDACTED = "[redacted]";
8
+ /** Rows a table may hold, per side, to be matched row by row, when the caller does not say. */
9
+ export declare const DEFAULT_BRANCH_DIFF_MAX_ROWS = 100000;
10
+ export interface ColumnFacts {
11
+ name: string;
12
+ type: string;
13
+ notNull: boolean;
14
+ defaultExpression: string | null;
15
+ /** `s` for a stored generated column. */
16
+ generated: string;
17
+ /** `a` for GENERATED ALWAYS, `d` for BY DEFAULT. */
18
+ identity: string;
19
+ sequence: string | null;
20
+ }
21
+ export interface PolicyFacts {
22
+ name: string;
23
+ permissive: string;
24
+ roles: string[];
25
+ command: string;
26
+ using: string | null;
27
+ withCheck: string | null;
28
+ }
29
+ /** An object of a table — a constraint, an index, a trigger — as the SQL that declares it. */
30
+ export interface NamedDefinition {
31
+ name: string;
32
+ definition: string;
33
+ }
34
+ export interface TableFacts {
35
+ schema: string;
36
+ table: string;
37
+ rls: boolean;
38
+ forceRls: boolean;
39
+ columns: ColumnFacts[];
40
+ primaryKey: string[];
41
+ policies: PolicyFacts[];
42
+ /** Unique, foreign-key, check and exclusion constraints: everything but the primary key and NOT NULL. */
43
+ constraints?: NamedDefinition[];
44
+ /** Indexes that back no constraint — a constraint's own index comes and goes with it. */
45
+ indexes?: NamedDefinition[];
46
+ /** Triggers someone wrote: not the ones Postgres makes for a foreign key. */
47
+ triggers?: NamedDefinition[];
48
+ }
49
+ /** Tables by `schema.table`, as a JSON pair so no name can collide with another. */
50
+ export type Catalog = Map<string, TableFacts>;
51
+ /** An object of a schema rather than a table: an enum type, a view, a function. */
52
+ export interface SchemaObjectFacts {
53
+ object: "enum" | "view" | "function";
54
+ schema: string;
55
+ /** Its name; a function's carries its argument types, as overloads differ by them. */
56
+ name: string;
57
+ definition: string;
58
+ }
59
+ /** Schema objects by kind, schema and name. */
60
+ export type SchemaObjects = Map<string, SchemaObjectFacts>;
61
+ /** A column as it would be declared in `CREATE TABLE`. */
62
+ export declare function columnDefinitionSql(column: ColumnFacts): string;
63
+ export declare function tableSql(facts: TableFacts): string;
64
+ export declare function policySql(schema: string, table: string, policy: PolicyFacts): string;
65
+ /**
66
+ * The schema changes from `target` to `branch`: tables, columns, primary keys,
67
+ * constraints, indexes, triggers, row-level security and policies, ordered by
68
+ * table.
69
+ */
70
+ export declare function diffCatalogs(target: Catalog, branch: Catalog): BranchSchemaChange[];
71
+ /** The enum types, views and functions added, dropped or altered from `target` to `branch`. */
72
+ export declare function diffSchemaObjects(target: SchemaObjects, branch: SchemaObjects): BranchSchemaChange[];
73
+ /** One row's key, the digest of its values, and whether it was written after the fork. */
74
+ export interface RowState {
75
+ key: string;
76
+ hash: string;
77
+ young: boolean;
78
+ }
79
+ export interface RowClassification {
80
+ inserted: string[];
81
+ updated: string[];
82
+ deleted: string[];
83
+ conflicts: string[];
84
+ targetChanges: number;
85
+ }
86
+ /**
87
+ * Who changed each row that differs — see the table in the module comment.
88
+ * Without a fork point every difference is the branch's.
89
+ */
90
+ export declare function classifyRows(target: readonly RowState[], branch: readonly RowState[], forkPointKnown: boolean): RowClassification;
91
+ /**
92
+ * The sides that rewrote the table since the fork, as far as its rows tell.
93
+ *
94
+ * `ALTER COLUMN … TYPE`, an `ADD COLUMN` with a volatile default, `VACUUM
95
+ * FULL`, `CLUSTER`, `pg_repack`: each writes every row anew, so every row of
96
+ * that side reads as written after the fork, and the provenance the
97
+ * classification rests on is gone. Its mark is a side whose every row is
98
+ * young while some of them hold exactly what the other side holds — rows
99
+ * nobody changed, written again.
100
+ */
101
+ export declare function rewrittenSides(target: readonly RowState[], branch: readonly RowState[]): ("target" | "branch")[];
102
+ /** Up to `limit` keys, taken in turn from each kind so that no kind crowds out the rest. */
103
+ export declare function pickSamples(kinds: readonly (readonly string[])[], limit: number): string[][];
104
+ /**
105
+ * What the branch `branchName` changed against the database it was made from:
106
+ * its schema and policies, and per table the rows it inserted, updated and
107
+ * deleted, with up to `sampleLimit` of them. Reads both databases and writes
108
+ * neither.
109
+ */
110
+ export declare function diffDatabaseBranch(admin: DatabaseAdmin | undefined, branchName: string, options?: BranchDiffOptions): Promise<BranchDiff>;
111
+ /**
112
+ * Apply the rows the branch `branchName` changed to the database it was made
113
+ * from. See `BranchMergeOptions` for what wins and when nothing is written.
114
+ */
115
+ export declare function mergeDatabaseBranch(admin: DatabaseAdmin | undefined, branchName: string, options: BranchMergeOptions): Promise<BranchMergeResult>;
@@ -1,16 +1,16 @@
1
1
  import { createRequire as __createRequire } from "module";
2
2
  __createRequire(import.meta.url);
3
- import { i as __toESM } from "./rolldown-runtime-V7rFbMRe.js";
4
- import { l as poolMaxCeiling } from "./connection-C938nR3E.js";
5
- import { t as openRebase } from "./open-rebase-BKA7Nyoe.js";
6
- import { a as diagnoseDbError } from "./cli-errors-DwQnNATs.js";
7
- import { i as outError, n as describeFailure, r as out } from "./source-Bv5KPeIx.js";
8
- import { n as require_arg } from "./cli-prompt-egtNKkZl.js";
9
- import { t as RELAYED_FLAGS } from "./cli-flags-D-b0qL9T.js";
3
+ import { n as require_arg, o as __toESM } from "./cli-prompt-DXG0yk-h.js";
4
+ import { l as poolMaxCeiling } from "./connection-czvI7TZL.js";
5
+ import { t as openRebase } from "./open-rebase-C8jQ6s5a.js";
6
+ import { a as diagnoseDbError } from "./cli-errors-DsYyNYzd.js";
7
+ import { i as outError, n as describeFailure, r as out } from "./source-fZb92pN8.js";
8
+ import { p as RELAYED_FLAGS } from "./migrate-baseline-B-2Ef4Mv.js";
10
9
  import { setLogOutput } from "@rebasepro/server";
11
10
  import path from "path";
12
11
  import fs from "fs";
13
12
  import tty from "node:tty";
13
+ import { pathToFileURL } from "node:url";
14
14
  import { inspect } from "node:util";
15
15
  import { PassThrough } from "node:stream";
16
16
  import readline from "node:readline";
@@ -120,18 +120,50 @@ function describeOpenFailure(error, databaseUrl, managed) {
120
120
  if (managed && (connectionCutOff(error) || noFreeConnection(error))) return "✗ The managed development database has no free connection: it accepts only a few at a time, and other clients hold them — a running `rebase dev` holds some.\n Close another `rebase console`, psql or database GUI and run this again, or stop `rebase dev` while you use the console.";
121
121
  return diagnoseDbError(error, databaseUrl) ?? describeFailure(error);
122
122
  }
123
+ /**
124
+ * The project's global callbacks: the `callbacks` its `config/index.ts`
125
+ * exports, which the server runs ahead of every collection's own hooks.
126
+ *
127
+ * The config package is the directory the collections live in
128
+ * (`<config>/collections`), or `../config` beside the backend for a
129
+ * collections directory named otherwise. No `index` there means no global
130
+ * callbacks, as it does for the server.
131
+ *
132
+ * One that is there and cannot be imported — an import that needs an
133
+ * environment variable the console was not given, say — does not stop the
134
+ * console, which can still read and write. It says so, once, and runs without
135
+ * them: writes from this session then skip the global tier, and the reader
136
+ * knows it.
137
+ */
138
+ async function loadProjectCallbacks(collectionsPath) {
139
+ const configDir = path.basename(collectionsPath) === "collections" ? path.dirname(collectionsPath) : path.resolve("..", "config");
140
+ const indexPath = [".js", ".ts"].map((ext) => path.join(configDir, `index${ext}`)).find((candidate) => fs.existsSync(candidate));
141
+ if (!indexPath) return void 0;
142
+ let mod;
143
+ try {
144
+ mod = await import(pathToFileURL(indexPath).href);
145
+ } catch (error) {
146
+ const reason = (error instanceof Error ? error.message : String(error)).split("\n")[0];
147
+ outError(`⚠ Global callbacks are off in this session: ${indexPath} could not be loaded (${reason}). Collection callbacks still run.`);
148
+ return;
149
+ }
150
+ const callbacks = mod.callbacks;
151
+ return callbacks && typeof callbacks === "object" ? callbacks : void 0;
152
+ }
123
153
  /** Open the project, run `--eval` or the REPL, and close it again. Resolves with the exit code. */
124
154
  async function openAndRun(options, streams) {
125
155
  const { databaseUrl } = options;
126
156
  const collectionsPath = path.resolve(options.collections || path.join("..", "config", "collections"));
127
- const { loadCollections } = await import("./doctor-DvJH-IQG.js").then((n) => n.t);
157
+ const { loadCollections } = await import("./doctor-DW_mlTzz.js").then((n) => n.t);
128
158
  const collections = fs.existsSync(collectionsPath) ? await loadCollections(collectionsPath) : [];
159
+ const callbacks = await loadProjectCallbacks(collectionsPath);
129
160
  const ceiling = poolMaxCeiling();
130
161
  let rebase;
131
162
  try {
132
163
  rebase = await openRebase({
133
164
  databaseUrl,
134
165
  collections,
166
+ ...callbacks ? { callbacks } : {},
135
167
  ...ceiling !== null ? { pool: { max: ceiling } } : {}
136
168
  });
137
169
  } catch (error) {
@@ -259,4 +291,4 @@ async function feedLineByLine(source, server, feed, exited) {
259
291
  //#endregion
260
292
  export { consoleCommand };
261
293
 
262
- //# sourceMappingURL=cli-console-DQ_K-sdM.js.map
294
+ //# sourceMappingURL=cli-console-f0k6_TsJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-console-f0k6_TsJ.js","names":[],"sources":["../src/cli-console.ts"],"sourcesContent":["/**\n * `rebase console` — the driver's half.\n *\n * Loads the project's collections the way `rebase db push` does, and the\n * global callbacks its `config/index.ts` exports, opens the database with\n * {@link openRebase}, and either evaluates one piece of code\n * (`--eval`) or starts a Node REPL with `rebase` and `collections` in scope.\n * The `rebase` CLI resolves the database first (the managed one included) and\n * hands this process `DATABASE_URL`, exactly as it does for `db` commands.\n *\n * This is the boot side of `openRebase`: it is where the environment is read\n * — `DATABASE_URL`, and the `REBASE_DB_POOL_MAX` ceiling the managed\n * development database needs — and turned into explicit options.\n */\nimport arg from \"arg\";\nimport fs from \"fs\";\nimport path from \"path\";\nimport readline from \"node:readline\";\nimport repl from \"node:repl\";\nimport { PassThrough } from \"node:stream\";\nimport tty from \"node:tty\";\nimport { pathToFileURL } from \"node:url\";\nimport { inspect } from \"node:util\";\nimport { setLogOutput } from \"@rebasepro/server\";\nimport type { CollectionCallbacks, CollectionConfig } from \"@rebasepro/types\";\nimport { RELAYED_FLAGS } from \"./cli-flags\";\nimport { diagnoseDbError } from \"./cli-errors\";\nimport { out, outError, describeFailure } from \"./cli-output\";\nimport { poolMaxCeiling } from \"./connection\";\nimport { openRebase, type OpenedRebase } from \"./open-rebase\";\n\n/**\n * The flags `rebase console` takes. `--history` is the CLI's own: where it\n * keeps the REPL's history (`.rebase/console_history` in the project).\n */\nexport const CONSOLE_FLAGS = {\n \"--collections\": String,\n \"-c\": \"--collections\",\n \"--eval\": String,\n \"-e\": \"--eval\",\n \"--history\": String\n} satisfies arg.Spec;\n\n/** What the code the console runs can name. */\nexport interface ConsoleScope {\n rebase: OpenedRebase;\n collections: CollectionConfig[];\n}\n\ntype ScopedFunction = (rebase: OpenedRebase, collections: CollectionConfig[]) => Promise<unknown>;\ntype AsyncFunctionConstructor = new (...parameters: string[]) => ScopedFunction;\n\n/** The constructor of `async function` — what `new Function` is for a body that awaits. */\nconst AsyncFunction: AsyncFunctionConstructor = Object.getPrototypeOf(async () => undefined).constructor;\n\n/**\n * Run `code` with `rebase` and `collections` in scope and resolve with its\n * value.\n *\n * Read as an expression first, so `rebase.dataAsAdmin.posts.find()` is its own\n * result; code that is not one expression runs as a function body, where\n * `await` works everywhere and `return` gives the result.\n */\nexport async function evaluateInConsole(code: string, scope: ConsoleScope): Promise<unknown> {\n let run: ScopedFunction;\n try {\n run = new AsyncFunction(\"rebase\", \"collections\", `return (${code}\\n);`);\n } catch (error) {\n if (!(error instanceof SyntaxError)) throw error;\n run = new AsyncFunction(\"rebase\", \"collections\", code);\n }\n return run(scope.rebase, scope.collections);\n}\n\n/** A result as JSON, the way `--eval` prints it: dates as ISO strings, bigints as strings. */\nexport function consoleJson(value: unknown): string {\n return JSON.stringify(value, (_key, item: unknown) => typeof item === \"bigint\" ? item.toString() : item, 2) ?? \"undefined\";\n}\n\n/** The host and database a URL names, for the banner — never its credentials. */\nfunction describeDatabase(databaseUrl: string): string {\n try {\n const url = new URL(databaseUrl);\n return `${url.hostname}${url.port ? `:${url.port}` : \"\"}${url.pathname}`;\n } catch {\n return \"the configured database\";\n }\n}\n\n/** Where the REPL reads and writes; the terminal unless a caller hands it streams. */\nexport interface ConsoleStreams {\n input?: NodeJS.ReadableStream;\n output?: NodeJS.WritableStream;\n}\n\n/**\n * `rebase console [--eval <code>] [--collections <dir>]`, from the driver\n * line `[\"console\", …flags]`. Resolves with the exit code.\n */\nexport async function consoleCommand(args: string[], streams: ConsoleStreams = {}): Promise<number> {\n const flags = arg({ ...RELAYED_FLAGS, ...CONSOLE_FLAGS }, { argv: args.slice(1) });\n if (flags[\"--help\"]) {\n out(\"Run `rebase console --help` for the options — this is the driver's own entry point.\");\n return 0;\n }\n\n const databaseUrl = flags[\"--database-url\"] || process.env.DATABASE_URL;\n if (!databaseUrl) {\n outError(\"✗ No database to open: DATABASE_URL is not set. Set it in .env, or pass --database-url.\");\n return 1;\n }\n\n // The console's own lines are what the reader is here for; the driver's\n // info-level boot notes are not, unless they asked for them.\n if (process.env.LOG_LEVEL === undefined) process.env.LOG_LEVEL = \"warn\";\n\n // And whatever level they asked for, a log line is never part of the\n // answer: stdout holds the `--eval` result a pipe reads (`| jq .`) and the\n // REPL's values. The scaffold's `.env` sets LOG_LEVEL=info, and under\n // NODE_ENV=production warnings go to stdout too.\n setLogOutput(\"stderr\");\n try {\n return await openAndRun({\n databaseUrl,\n collections: flags[\"--collections\"],\n eval: flags[\"--eval\"],\n history: flags[\"--history\"]\n }, streams);\n } finally {\n setLogOutput();\n }\n}\n\n/** Whether a connection was reset or cut off before it answered, anywhere in the error's chain. */\nfunction connectionCutOff(error: unknown, depth = 0): boolean {\n if (!error || typeof error !== \"object\" || depth > 4) return false;\n if (\"code\" in error && error.code === \"ECONNRESET\") return true;\n if (error instanceof Error && /Connection terminated unexpectedly/.test(error.message)) return true;\n if (\"errors\" in error && Array.isArray(error.errors) && error.errors.some(inner => connectionCutOff(inner, depth + 1))) return true;\n return \"cause\" in error && connectionCutOff(error.cause, depth + 1);\n}\n\n/** Whether Postgres refused the connection for want of one: SQLSTATE 53300, anywhere in the error's chain. */\nfunction noFreeConnection(error: unknown, depth = 0): boolean {\n if (!error || typeof error !== \"object\" || depth > 4) return false;\n if (\"code\" in error && error.code === \"53300\") return true;\n return \"cause\" in error && noFreeConnection(error.cause, depth + 1);\n}\n\n/**\n * What the console says when the project's database will not open: the banner\n * every db command prints for a database it cannot reach, or one sentence —\n * never the driver's error serialised onto one line, stack and all.\n *\n * The managed development database accepts only a few connections at a time,\n * and a running `rebase dev` holds some of them. Its proxy refuses one over the\n * limit with SQLSTATE 53300; a database an earlier CLI started resets it\n * instead, and `read ECONNRESET` says nothing about why.\n */\nexport function describeOpenFailure(error: unknown, databaseUrl: string, managed: boolean): string {\n if (managed && (connectionCutOff(error) || noFreeConnection(error))) {\n return \"✗ The managed development database has no free connection: it accepts only a few at a time, \" +\n \"and other clients hold them — a running `rebase dev` holds some.\\n\" +\n \" Close another `rebase console`, psql or database GUI and run this again, \" +\n \"or stop `rebase dev` while you use the console.\";\n }\n return diagnoseDbError(error, databaseUrl) ?? describeFailure(error);\n}\n\n/**\n * The project's global callbacks: the `callbacks` its `config/index.ts`\n * exports, which the server runs ahead of every collection's own hooks.\n *\n * The config package is the directory the collections live in\n * (`<config>/collections`), or `../config` beside the backend for a\n * collections directory named otherwise. No `index` there means no global\n * callbacks, as it does for the server.\n *\n * One that is there and cannot be imported — an import that needs an\n * environment variable the console was not given, say — does not stop the\n * console, which can still read and write. It says so, once, and runs without\n * them: writes from this session then skip the global tier, and the reader\n * knows it.\n */\nexport async function loadProjectCallbacks(collectionsPath: string): Promise<CollectionCallbacks | undefined> {\n const configDir = path.basename(collectionsPath) === \"collections\"\n ? path.dirname(collectionsPath)\n : path.resolve(\"..\", \"config\");\n // `.js` first, as the server's own loader reads it.\n const indexPath = [\".js\", \".ts\"]\n .map(ext => path.join(configDir, `index${ext}`))\n .find(candidate => fs.existsSync(candidate));\n if (!indexPath) return undefined;\n\n let mod: Record<string, unknown>;\n try {\n mod = await import(pathToFileURL(indexPath).href) as Record<string, unknown>;\n } catch (error) {\n const reason = (error instanceof Error ? error.message : String(error)).split(\"\\n\")[0];\n outError(`⚠ Global callbacks are off in this session: ${indexPath} could not be loaded (${reason}). Collection callbacks still run.`);\n return undefined;\n }\n const callbacks = mod.callbacks;\n return callbacks && typeof callbacks === \"object\" ? callbacks as CollectionCallbacks : undefined;\n}\n\n/** Open the project, run `--eval` or the REPL, and close it again. Resolves with the exit code. */\nasync function openAndRun(\n options: { databaseUrl: string; collections?: string; eval?: string; history?: string },\n streams: ConsoleStreams\n): Promise<number> {\n const { databaseUrl } = options;\n const collectionsPath = path.resolve(options.collections || path.join(\"..\", \"config\", \"collections\"));\n const { loadCollections } = await import(\"./schema/doctor\");\n const collections = fs.existsSync(collectionsPath) ? await loadCollections(collectionsPath) : [];\n // Writes from the console run the same hook tiers as a server write.\n const callbacks = await loadProjectCallbacks(collectionsPath);\n\n const ceiling = poolMaxCeiling();\n let rebase: OpenedRebase;\n try {\n rebase = await openRebase({\n databaseUrl,\n collections,\n ...(callbacks ? { callbacks } : {}),\n ...(ceiling !== null ? { pool: { max: ceiling } } : {})\n });\n } catch (error) {\n outError(describeOpenFailure(error, databaseUrl, process.env.REBASE_DEV_DATABASE_KIND === \"managed\"));\n return 1;\n }\n\n try {\n const code = options.eval;\n if (code !== undefined) {\n try {\n const result = await evaluateInConsole(code, { rebase, collections: rebase.collections });\n if (result !== undefined) out(consoleJson(result));\n return 0;\n } catch (error) {\n outError(describeFailure(error));\n return 1;\n }\n }\n\n await startRepl({ rebase, collections: rebase.collections }, {\n history: options.history,\n database: describeDatabase(databaseUrl),\n ...streams\n });\n return 0;\n } finally {\n await rebase.close();\n }\n}\n\n/** The interactive half: a Node REPL until `.exit`, Ctrl-D or the end of piped input. */\nasync function startRepl(scope: ConsoleScope, options: { history?: string; database: string } & ConsoleStreams): Promise<void> {\n const input = options.input ?? process.stdin;\n const output = options.output ?? process.stdout;\n const typed = input instanceof tty.ReadStream && input.isTTY;\n\n // For a person at a terminal. Piped, stdout is the REPL's answers, which\n // whatever reads the pipe is after, so the banner goes to stderr with the\n // rest of what the console says about itself.\n const say = typed ? out : outError;\n say(\"\");\n say(` rebase console — ${options.database}, ${scope.collections.length} collection(s)`);\n say(\" In scope: rebase (dataAsAdmin, sql, withAuth, close) and collections. Top-level await works.\");\n say(\" Try: await rebase.dataAsAdmin.<collection>.find({ limit: 5 })\");\n say(\"\");\n\n // Typed at a terminal, the REPL reads it as one. Anything else — a pipe, a\n // file, a stream handed in — reaches it a line at a time through `feed`\n // (see `feedLineByLine`).\n const feed = typed ? undefined : new PassThrough();\n // Colours for a terminal only: piped to a file or another program, the\n // escape codes are noise in the text.\n const colors = output instanceof tty.WriteStream && output.isTTY;\n // Started without a prompt, and given one once the scope is in place: a\n // line that arrives with the first prompt must already see `rebase`.\n const server = repl.start({\n prompt: \"\",\n useGlobal: false,\n ...(feed ? { input: feed, output, terminal: false } : { input, output }),\n writer: (value: unknown) => inspect(value, { colors, depth: 6 })\n });\n server.context.rebase = scope.rebase;\n server.context.collections = scope.collections;\n server.setPrompt(\"rebase> \");\n\n const history = options.history;\n if (history) {\n fs.mkdirSync(path.dirname(history), { recursive: true });\n await new Promise<void>((resolve) => {\n server.setupHistory(history, (error) => {\n if (error) outError(` (history is off: ${error.message})`);\n resolve();\n });\n });\n }\n\n const exited = new Promise<void>((resolve) => server.once(\"exit\", () => resolve()));\n server.displayPrompt();\n if (feed) await feedLineByLine(input, server, feed, exited);\n await exited;\n}\n\n/**\n * Hand input that is not typed at a terminal to the REPL a line at a time,\n * each once the REPL has finished the one before.\n *\n * Typed, the REPL holds keystrokes back while a top-level `await` is pending.\n * Piped, every line arrives at once and each is evaluated as it arrives, so in\n * `const { data } = await …` followed by `data.map(…)` the second line read\n * `data` before the first had assigned it.\n *\n * The REPL is paused as each line reaches it, and it resumes its input when it\n * displays its prompt again: once the line's value has settled, its error has\n * been printed, or it needs the next line to complete it. That resumption is\n * when the next line goes in. Lines are written one per chunk, because a\n * chunk holding several is split and every line of it handed over at once.\n */\nasync function feedLineByLine(\n source: NodeJS.ReadableStream,\n server: repl.REPLServer,\n feed: PassThrough,\n exited: Promise<void>\n): Promise<void> {\n let evaluated: (() => void) | undefined;\n const ready = () => {\n const resolve = evaluated;\n evaluated = undefined;\n resolve?.();\n };\n // Before the REPL's own handler, so a line it evaluates at once resumes\n // after this pause rather than before it.\n server.prependListener(\"line\", () => {\n if (!server.editorMode) server.pause();\n });\n // `.editor` collects lines with no prompt until Ctrl-D: they go straight in.\n server.on(\"line\", () => {\n if (!server.editorMode) return;\n server.resume();\n ready();\n });\n server.on(\"resume\", ready);\n\n const closed = exited.then(() => \"closed\" as const);\n const lines = readline.createInterface({ input: source, crlfDelay: Infinity });\n for await (const line of lines) {\n const done = new Promise<\"done\">((resolve) => { evaluated = () => resolve(\"done\"); });\n feed.write(`${line}\\n`);\n // `.exit`: the rest of the input is not for this REPL.\n if (await Promise.race([done, closed]) === \"closed\") break;\n }\n feed.end();\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import type { CollectionConfig } from "@rebasepro/types";
1
+ import type { CollectionCallbacks, CollectionConfig } from "@rebasepro/types";
2
2
  import { type OpenedRebase } from "./open-rebase.js";
3
3
  /**
4
4
  * The flags `rebase console` takes. `--history` is the CLI's own: where it
@@ -48,3 +48,19 @@ export declare function consoleCommand(args: string[], streams?: ConsoleStreams)
48
48
  * instead, and `read ECONNRESET` says nothing about why.
49
49
  */
50
50
  export declare function describeOpenFailure(error: unknown, databaseUrl: string, managed: boolean): string;
51
+ /**
52
+ * The project's global callbacks: the `callbacks` its `config/index.ts`
53
+ * exports, which the server runs ahead of every collection's own hooks.
54
+ *
55
+ * The config package is the directory the collections live in
56
+ * (`<config>/collections`), or `../config` beside the backend for a
57
+ * collections directory named otherwise. No `index` there means no global
58
+ * callbacks, as it does for the server.
59
+ *
60
+ * One that is there and cannot be imported — an import that needs an
61
+ * environment variable the console was not given, say — does not stop the
62
+ * console, which can still read and write. It says so, once, and runs without
63
+ * them: writes from this session then skip the global tier, and the reader
64
+ * knows it.
65
+ */
66
+ export declare function loadProjectCallbacks(collectionsPath: string): Promise<CollectionCallbacks | undefined>;