@rebasepro/server-postgres 0.23.0 → 0.24.1-canary.g19596ef

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/README.md +16 -0
  2. package/dist/{BranchService-DGPL6G_C.js → BranchService-UgKa416x.js} +47 -19
  3. package/dist/BranchService-UgKa416x.js.map +1 -0
  4. package/dist/PostgresBackendDriver.d.ts +187 -13
  5. package/dist/PostgresBootstrapper.d.ts +101 -2
  6. package/dist/auth/ensure-tables.d.ts +12 -0
  7. package/dist/auth/services.d.ts +78 -14
  8. package/dist/{auth-users-columns-D2LBFrMH.js → auth-users-columns-C72EMoDJ.js} +17 -1
  9. package/dist/{auth-users-columns-D2LBFrMH.js.map → auth-users-columns-C72EMoDJ.js.map} +1 -1
  10. package/dist/backup/backup-cli.d.ts +75 -2
  11. package/dist/backup/backup-service.d.ts +2 -2
  12. package/dist/backup/pg-binary.d.ts +5 -0
  13. package/dist/{backup-cli-24v4OlSp.js → backup-cli-yiKd1GsC.js} +168 -75
  14. package/dist/backup-cli-yiKd1GsC.js.map +1 -0
  15. package/dist/{backup-service-HQ9GC4tN.js → backup-service-AZWc2wvI.js} +104 -35
  16. package/dist/backup-service-AZWc2wvI.js.map +1 -0
  17. package/dist/boot-ddl-B9ZmBJ75.js +105 -0
  18. package/dist/boot-ddl-B9ZmBJ75.js.map +1 -0
  19. package/dist/cli-console-DGee3Zl7.js +225 -0
  20. package/dist/cli-console-DGee3Zl7.js.map +1 -0
  21. package/dist/cli-console.d.ts +39 -0
  22. package/dist/cli-errors-oVUuD8k0.js +2180 -0
  23. package/dist/cli-errors-oVUuD8k0.js.map +1 -0
  24. package/dist/cli-errors.d.ts +133 -9
  25. package/dist/{cli-flags-BglvpjHv.js → cli-flags-DnPHXjRH.js} +56 -6
  26. package/dist/cli-flags-DnPHXjRH.js.map +1 -0
  27. package/dist/cli-flags.d.ts +15 -0
  28. package/dist/cli-helpers.d.ts +88 -34
  29. package/dist/cli-output.d.ts +16 -28
  30. package/dist/{arg-Dni7MzLB.js → cli-prompt-egtNKkZl.js} +49 -3
  31. package/dist/cli-prompt-egtNKkZl.js.map +1 -0
  32. package/dist/cli-prompt.d.ts +19 -0
  33. package/dist/cli-push.d.ts +87 -0
  34. package/dist/cli-scratch-database.d.ts +33 -8
  35. package/dist/cli.js +826 -230
  36. package/dist/cli.js.map +1 -1
  37. package/dist/{column-plan-helpers-DF-8dTVa.js → column-plan-helpers-D_V4oaZh.js} +43 -27
  38. package/dist/column-plan-helpers-D_V4oaZh.js.map +1 -0
  39. package/dist/{databasePoolManager-Bj5FbeAs.js → connection-C938nR3E.js} +25 -113
  40. package/dist/connection-C938nR3E.js.map +1 -0
  41. package/dist/connection.d.ts +13 -2
  42. package/dist/data-transformer.d.ts +0 -8
  43. package/dist/databasePoolManager-DZgnBOgj.js +119 -0
  44. package/dist/databasePoolManager-DZgnBOgj.js.map +1 -0
  45. package/dist/deferred-index-build-BqmEHGaD.js +1051 -0
  46. package/dist/deferred-index-build-BqmEHGaD.js.map +1 -0
  47. package/dist/{doctor-Cb2thZ8s.js → doctor-CUAAOFZm.js} +281 -89
  48. package/dist/doctor-CUAAOFZm.js.map +1 -0
  49. package/dist/ensure-collection-policies-Bb51V2OV.js +541 -0
  50. package/dist/ensure-collection-policies-Bb51V2OV.js.map +1 -0
  51. package/dist/ensure-collection-tables-egjGBjhv.js +532 -0
  52. package/dist/ensure-collection-tables-egjGBjhv.js.map +1 -0
  53. package/dist/{ensure-tables-Dhn9KM3B.js → ensure-tables-DMPWhJ6R.js} +180 -77
  54. package/dist/ensure-tables-DMPWhJ6R.js.map +1 -0
  55. package/dist/enum-labels-kept-Msq5gMcb.js +56 -0
  56. package/dist/enum-labels-kept-Msq5gMcb.js.map +1 -0
  57. package/dist/generate-drizzle-schema-DrA-YgXv.js +57 -0
  58. package/dist/generate-drizzle-schema-DrA-YgXv.js.map +1 -0
  59. package/dist/{generate-drizzle-schema-logic-BfzK7UQd.js → generate-drizzle-schema-logic-BaT0H7Gq.js} +29 -11
  60. package/dist/generate-drizzle-schema-logic-BaT0H7Gq.js.map +1 -0
  61. package/dist/generate-files-OoRJLEdk.js +199 -0
  62. package/dist/generate-files-OoRJLEdk.js.map +1 -0
  63. package/dist/generate-postgres-ddl-logic-B4k9djAV.js +152 -0
  64. package/dist/generate-postgres-ddl-logic-B4k9djAV.js.map +1 -0
  65. package/dist/{generated-schema-staleness-Di9c4ZxN.js → generated-schema-staleness-BWN_PsMi.js} +2 -2
  66. package/dist/{generated-schema-staleness-Di9c4ZxN.js.map → generated-schema-staleness-BWN_PsMi.js.map} +1 -1
  67. package/dist/generated-sql.d.ts +34 -0
  68. package/dist/history/HistoryService.d.ts +64 -5
  69. package/dist/history/ensure-history-table.d.ts +63 -4
  70. package/dist/index.d.ts +1 -0
  71. package/dist/index.es.js +139 -16738
  72. package/dist/index.es.js.map +1 -1
  73. package/dist/{introspect-db-logic-WMuAfxvw.js → introspect-db-logic-BCc1Jym2.js} +654 -353
  74. package/dist/introspect-db-logic-BCc1Jym2.js.map +1 -0
  75. package/dist/introspect-db-queries-BhhJg0b8.js +364 -0
  76. package/dist/introspect-db-queries-BhhJg0b8.js.map +1 -0
  77. package/dist/open-rebase-CAjWpPjs.js +20895 -0
  78. package/dist/open-rebase-CAjWpPjs.js.map +1 -0
  79. package/dist/open-rebase.d.ts +94 -0
  80. package/dist/pg-error-utils-Bs_Ue4wn.js +395 -0
  81. package/dist/pg-error-utils-Bs_Ue4wn.js.map +1 -0
  82. package/dist/{plan-schema-C0fxM8dY.js → plan-schema-CsNgK0qi.js} +279 -534
  83. package/dist/plan-schema-CsNgK0qi.js.map +1 -0
  84. package/dist/{policy-drift-DljYdrpW.js → policy-drift-BKC50y2p.js} +33 -9
  85. package/dist/policy-drift-BKC50y2p.js.map +1 -0
  86. package/dist/push-index-prebuild-C_qhyoJa.js +45 -0
  87. package/dist/push-index-prebuild-C_qhyoJa.js.map +1 -0
  88. package/dist/push-legacy-renames-5g9h3HOa.js +34 -0
  89. package/dist/push-legacy-renames-5g9h3HOa.js.map +1 -0
  90. package/dist/{generate-postgres-ddl-logic-Bt2d2mRH.js → render-ddl-CrfEe__B.js} +24 -157
  91. package/dist/render-ddl-CrfEe__B.js.map +1 -0
  92. package/dist/{rls-bootstrap-sql-B8EclDyM.js → rls-bootstrap-sql-Bd-hJk08.js} +125 -1101
  93. package/dist/rls-bootstrap-sql-Bd-hJk08.js.map +1 -0
  94. package/dist/{rls-enforcement-C6Xk0lA6.js → rls-enforcement-D7P4-ZYX.js} +138 -18
  95. package/dist/rls-enforcement-D7P4-ZYX.js.map +1 -0
  96. package/dist/{__vite-browser-external-BnuHet1e.js → rolldown-runtime-V7rFbMRe.js} +1 -8
  97. package/dist/schema/atlas-exclude.d.ts +42 -0
  98. package/dist/schema/auth-schema.d.ts +172 -2
  99. package/dist/schema/boot-ddl.d.ts +47 -0
  100. package/dist/schema/carved-out-migration.d.ts +5 -4
  101. package/dist/schema/classify-change.d.ts +29 -1
  102. package/dist/schema/collection-index.d.ts +45 -0
  103. package/dist/schema/column-plan-helpers.d.ts +38 -21
  104. package/dist/schema/deferred-index-build.d.ts +131 -0
  105. package/dist/schema/destructive-sql.d.ts +50 -2
  106. package/dist/schema/doctor-cli.js +110 -6
  107. package/dist/schema/doctor-cli.js.map +1 -1
  108. package/dist/schema/doctor-search-scale.d.ts +45 -0
  109. package/dist/schema/doctor.d.ts +53 -1
  110. package/dist/schema/ensure-collection-policies.d.ts +95 -35
  111. package/dist/schema/ensure-collection-tables.d.ts +56 -3
  112. package/dist/schema/enum-labels-kept.d.ts +26 -0
  113. package/dist/schema/generate-drizzle-schema.js +1 -1
  114. package/dist/schema/generate-files.d.ts +44 -0
  115. package/dist/schema/generate-next-step.d.ts +0 -13
  116. package/dist/schema/generate-postgres-ddl-logic.d.ts +5 -5
  117. package/dist/schema/generate-postgres-ddl.js +10 -81
  118. package/dist/schema/generate-postgres-ddl.js.map +1 -1
  119. package/dist/schema/generate-schema-commit.d.ts +12 -0
  120. package/dist/schema/generated-schema-diff.d.ts +7 -0
  121. package/dist/schema/introspect-db-logic.d.ts +63 -0
  122. package/dist/schema/introspect-db-queries.d.ts +21 -2
  123. package/dist/schema/introspect-db-search.d.ts +22 -0
  124. package/dist/schema/introspect-db-storage.d.ts +83 -0
  125. package/dist/schema/introspect-db-structure.d.ts +10 -0
  126. package/dist/schema/introspect-db.js +29 -21
  127. package/dist/schema/introspect-db.js.map +1 -1
  128. package/dist/schema/migrate-baseline.d.ts +33 -0
  129. package/dist/schema/migration-post-process.d.ts +34 -0
  130. package/dist/schema/plan/diff-plan.d.ts +127 -1
  131. package/dist/schema/plan/plan-schema.d.ts +16 -8
  132. package/dist/schema/plan/render-ddl.d.ts +8 -1
  133. package/dist/schema/plan/types.d.ts +45 -11
  134. package/dist/schema/push-index-prebuild.d.ts +25 -0
  135. package/dist/schema/push-legacy-renames.d.ts +20 -0
  136. package/dist/schema/rls-bootstrap-sql.d.ts +15 -0
  137. package/dist/schema/undeclarable-constraints.d.ts +59 -0
  138. package/dist/search-column-BM-GV6vH.js +442 -0
  139. package/dist/search-column-BM-GV6vH.js.map +1 -0
  140. package/dist/security/policy-drift.d.ts +1 -1
  141. package/dist/security/rls-enforcement.d.ts +39 -4
  142. package/dist/services/BranchService.d.ts +29 -1
  143. package/dist/services/FetchService.d.ts +63 -12
  144. package/dist/services/PersistService.d.ts +15 -0
  145. package/dist/services/RelationService.d.ts +106 -3
  146. package/dist/services/cdc/CdcListener.d.ts +26 -10
  147. package/dist/services/cdc/identity-columns.d.ts +19 -0
  148. package/dist/services/cdc/trigger-cdc.d.ts +180 -18
  149. package/dist/services/channel-bus/PostgresChannelBus.d.ts +12 -7
  150. package/dist/services/channel-bus/index.d.ts +1 -1
  151. package/dist/services/channel-history.d.ts +32 -5
  152. package/dist/services/collection-helpers.d.ts +26 -1
  153. package/dist/services/field-viewer.d.ts +15 -1
  154. package/dist/services/pg-notify-listener.d.ts +95 -6
  155. package/dist/services/realtimeService.d.ts +467 -53
  156. package/dist/services/row-pipeline.d.ts +16 -9
  157. package/dist/services/socket-liveness.d.ts +85 -0
  158. package/dist/services/soft-delete.d.ts +12 -0
  159. package/dist/services/sql-script.d.ts +275 -0
  160. package/dist/services/vector-iterative-scan.d.ts +40 -0
  161. package/dist/services/write-depth.d.ts +22 -0
  162. package/dist/services/write-transaction-scope.d.ts +15 -3
  163. package/dist/socket-opt-ins.d.ts +64 -0
  164. package/dist/source-DB6xJv3p.js +873 -0
  165. package/dist/source-DB6xJv3p.js.map +1 -0
  166. package/dist/tsx-loader.d.ts +14 -0
  167. package/dist/utils/connection-string.d.ts +31 -0
  168. package/dist/utils/drizzle-conditions.d.ts +63 -3
  169. package/dist/utils/error-cause.d.ts +7 -0
  170. package/dist/utils/pg-error-utils.d.ts +2 -4
  171. package/dist/utils/result-rows.d.ts +10 -0
  172. package/dist/{vector-index-CMJKb3C9.js → vector-index-BFhyEaiz.js} +2 -2
  173. package/dist/{vector-index-CMJKb3C9.js.map → vector-index-BFhyEaiz.js.map} +1 -1
  174. package/dist/websocket-DUiiwQd2.js +1632 -0
  175. package/dist/websocket-DUiiwQd2.js.map +1 -0
  176. package/dist/websocket.d.ts +63 -18
  177. package/package.json +7 -7
  178. package/dist/BranchService-DGPL6G_C.js.map +0 -1
  179. package/dist/__vite-browser-external-BnuHet1e.js.map +0 -1
  180. package/dist/arg-Dni7MzLB.js.map +0 -1
  181. package/dist/backup-cli-24v4OlSp.js.map +0 -1
  182. package/dist/backup-service-HQ9GC4tN.js.map +0 -1
  183. package/dist/cli-errors-B8qHg02P.js +0 -798
  184. package/dist/cli-errors-B8qHg02P.js.map +0 -1
  185. package/dist/cli-flags-BglvpjHv.js.map +0 -1
  186. package/dist/cli-output-CNdMql-L.js +0 -47
  187. package/dist/cli-output-CNdMql-L.js.map +0 -1
  188. package/dist/collection-index-DdnsxiJ_.js +0 -286
  189. package/dist/collection-index-DdnsxiJ_.js.map +0 -1
  190. package/dist/column-plan-helpers-DF-8dTVa.js.map +0 -1
  191. package/dist/databasePoolManager-Bj5FbeAs.js.map +0 -1
  192. package/dist/doctor-Cb2thZ8s.js.map +0 -1
  193. package/dist/ensure-collection-policies-RHUcEp4v.js +0 -105
  194. package/dist/ensure-collection-policies-RHUcEp4v.js.map +0 -1
  195. package/dist/ensure-collection-tables-BEEjn5cn.js +0 -857
  196. package/dist/ensure-collection-tables-BEEjn5cn.js.map +0 -1
  197. package/dist/ensure-tables-Dhn9KM3B.js.map +0 -1
  198. package/dist/generate-drizzle-schema-logic-BfzK7UQd.js.map +0 -1
  199. package/dist/generate-drizzle-schema-yzY_BLhr.js +0 -123
  200. package/dist/generate-drizzle-schema-yzY_BLhr.js.map +0 -1
  201. package/dist/generate-postgres-ddl-logic-Bt2d2mRH.js.map +0 -1
  202. package/dist/introspect-db-logic-WMuAfxvw.js.map +0 -1
  203. package/dist/plan-schema-C0fxM8dY.js.map +0 -1
  204. package/dist/policy-drift-DljYdrpW.js.map +0 -1
  205. package/dist/rls-bootstrap-sql-B8EclDyM.js.map +0 -1
  206. package/dist/rls-enforcement-C6Xk0lA6.js.map +0 -1
  207. package/dist/source-Br7L7GOI.js +0 -410
  208. package/dist/source-Br7L7GOI.js.map +0 -1
@@ -4,7 +4,7 @@
4
4
  * Implements the `BackendBootstrapper` interface for PostgreSQL.
5
5
  */
6
6
  import { NodePgDatabase } from "drizzle-orm/node-postgres";
7
- import { BackendBootstrapper, CollectionConfig, type RealtimeChannelsConfig } from "@rebasepro/types";
7
+ import { BackendBootstrapper, CollectionConfig, InitializedDriver, type DatabaseAdapterInitConfig, type RealtimeChannelsConfig } from "@rebasepro/types";
8
8
  import { PostgresBackendDriver } from "./PostgresBackendDriver.js";
9
9
  import { RealtimeService } from "./services/realtimeService.js";
10
10
  import { DatabasePoolManager } from "./databasePoolManager.js";
@@ -14,7 +14,27 @@ import { type UnknownFilterFieldsMode } from "./utils/drizzle-conditions.js";
14
14
  export interface PostgresDriverConfig {
15
15
  connectionString?: string;
16
16
  adminConnectionString?: string;
17
+ /**
18
+ * This database's read replica: reads are routed there, writes stay on
19
+ * the primary. Unset, the boot ({@link postgresDriverConfigFromEnv}) fills
20
+ * it from `DATABASE_READ_URL` for the default source only; any other
21
+ * source has none unless its config names one — the managed runtime
22
+ * resolves `DATABASE_READ_URL__<KEY>` into this for each source.
23
+ */
17
24
  readConnectionString?: string;
25
+ /**
26
+ * A direct (non-pooled) URL to this database, for the connections that
27
+ * hold a `LISTEN`: change capture, cross-instance realtime, the Postgres
28
+ * channel bus and `admin.listen`. A transaction-mode pooler such as
29
+ * PgBouncer does not keep a `LISTEN`, so behind one this has to name the
30
+ * database itself.
31
+ *
32
+ * Unset, the boot fills it from `DATABASE_DIRECT_URL` for the default
33
+ * source only, and any other source listens on its own
34
+ * `connectionString` — see {@link resolveSourceUrls}. The managed runtime
35
+ * resolves `DATABASE_DIRECT_URL__<KEY>` into this for each source.
36
+ */
37
+ directConnectionString?: string;
18
38
  connection?: unknown;
19
39
  /**
20
40
  * The project's generated Drizzle module, for **diagnostics only**.
@@ -37,13 +57,17 @@ export interface PostgresDriverConfig {
37
57
  */
38
58
  introspectionSchema?: string;
39
59
  /**
40
- * Realtime options, both opt-in:
60
+ * Realtime options, among them:
41
61
  *
42
62
  * - `channels` — retention. Without rules no channel keeps any history and
43
63
  * broadcast stays fire-and-forget. See {@link ChannelRetentionRule}.
44
64
  * - `bus` — the cross-instance transport for channel broadcast and
45
65
  * presence. Defaults to in-process only, which is correct for a single
46
66
  * instance and wrong for two. See {@link ChannelBusConfig}.
67
+ * - `cdc` — how changes are captured (`auto` by default).
68
+ *
69
+ * At boot each has an environment variable that wins over it — see
70
+ * {@link postgresDriverConfigFromEnv}.
47
71
  */
48
72
  realtime?: RealtimeChannelsConfig;
49
73
  /**
@@ -65,6 +89,13 @@ export interface PostgresDriverInternals {
65
89
  realtimeService: RealtimeService;
66
90
  driver: PostgresBackendDriver;
67
91
  poolManager?: DatabasePoolManager;
92
+ /**
93
+ * Whether the driver was opened with provisioning (`provision`, default
94
+ * true). The hooks that run after it — auth, history, the final security
95
+ * posture — read it, so `provision: false` means no DDL anywhere on the
96
+ * driver's path, not only while it opens.
97
+ */
98
+ provisioned: boolean;
68
99
  /**
69
100
  * Attach CDC triggers to tables that did not exist when the driver
70
101
  * bootstrapped. Only set when database-level capture is actually active.
@@ -118,6 +149,27 @@ export declare function describeSchemaDriftCause(provisioning: {
118
149
  attempted: boolean;
119
150
  reason?: string;
120
151
  } | undefined): string[];
152
+ /**
153
+ * The URLs one source's driver opens besides its pool: the read replica, and
154
+ * the direct connection its `LISTEN`s are held on.
155
+ *
156
+ * The bare `DATABASE_READ_URL` and `DATABASE_DIRECT_URL` belong to the default
157
+ * source. Both used to be read here for every source, so a second database
158
+ * (`database("analytics")`) held its change-capture `LISTEN` on the default
159
+ * database — where none of its notifications are sent, so its realtime feed
160
+ * was silent — and read through the default's replica. A keyed source gets
161
+ * the URLs its config names, and otherwise its own connection and no replica.
162
+ *
163
+ * The default source still falls back to the environment: an application that
164
+ * builds this adapter itself passes a `connectionString` and nothing else, and
165
+ * has always had those two variables honoured.
166
+ *
167
+ * Exported for its own test; the caller needs a live database.
168
+ */
169
+ export declare function resolveSourceUrls(pgConfig: Pick<PostgresDriverConfig, "connectionString" | "readConnectionString" | "directConnectionString">, sourceKey: string, env?: Record<string, string | undefined>): {
170
+ readUrl?: string;
171
+ directUrl?: string;
172
+ };
121
173
  /**
122
174
  * Is this the local database `rebase init` scaffolds — i.e. the one case where
123
175
  * "you are connected as a superuser" is not news?
@@ -144,4 +196,51 @@ export declare function describeSchemaDriftCause(provisioning: {
144
196
  * ships `NODE_ENV=development` and some deployments inherit it.
145
197
  */
146
198
  export declare function isScaffoldedLocalDatabase(connectionString: string | undefined): boolean;
199
+ /**
200
+ * `config` with the environment read into it, the way the server's boot reads
201
+ * it. The one place this driver reads environment variables:
202
+ *
203
+ * | variable | setting |
204
+ * |----------------------------------------|----------------------------------------|
205
+ * | `DATABASE_READ_URL` | `readConnectionString`, when unset (*) |
206
+ * | `DATABASE_DIRECT_URL` | `directConnectionString`, when unset (*)|
207
+ * | `REALTIME_CDC` | `realtime.cdc` |
208
+ * | `REALTIME_CHANNEL_BUS` | `realtime.bus` (a named transport) |
209
+ * | `REALTIME_MAX_SUBSCRIPTIONS_PER_SOCKET`| `realtime.maxSubscriptionsPerSocket` |
210
+ * | `REALTIME_MAX_SOCKETS_PER_USER` | `realtime.maxSocketsPerUser` |
211
+ * | `REALTIME_REFETCH_MAX_WAIT_MS` | `realtime.refetchMaxWaitMs` |
212
+ * | `REALTIME_RECOUNT_INTERVAL_MS` | `realtime.recountIntervalMs` |
213
+ * | `REALTIME_RECOUNT_MIN_ROWS` | `realtime.recountMinRows` |
214
+ *
215
+ * A `REALTIME_*` variable that is set wins over the option, so a deployment
216
+ * can change it without a rebuild; the URLs fill in only what the config left
217
+ * empty. A mistyped number throws, naming the variable.
218
+ *
219
+ * (*) For the default source only. The bare URL variables name the default
220
+ * database; read for a second source (`database("analytics")`), they put its
221
+ * change-capture `LISTEN` on the default database, where none of its
222
+ * notifications arrive, and its reads on the default's replica. A keyed
223
+ * source's URLs come from its own config — the managed runtime passes
224
+ * `DATABASE_*_URL__<KEY>` there — see {@link resolveSourceUrls}.
225
+ *
226
+ * `createPostgresAdapter` applies this when the server boots, and
227
+ * {@link openPostgresDriver} does not — a script that opens the driver itself
228
+ * gets exactly the config it passed.
229
+ */
230
+ export declare function postgresDriverConfigFromEnv(config: PostgresDriverConfig, env?: NodeJS.ProcessEnv, sourceKey?: string): PostgresDriverConfig;
231
+ /**
232
+ * Open the Postgres data driver from explicit settings.
233
+ *
234
+ * What `createPostgresAdapter` runs when the server boots, once it has read
235
+ * the environment into `pgConfig` (see {@link postgresDriverConfigFromEnv});
236
+ * this function reads no environment variable itself. `config` says what the
237
+ * caller wants opened: which collections, whether opening may change the
238
+ * database (`provision`, default true), and what part this process takes in
239
+ * realtime (`realtime`, or `false` for none at all).
240
+ *
241
+ * `openRebase` is this with `provision: false` and `realtime: false`.
242
+ */
243
+ export declare function openPostgresDriver(pgConfig: PostgresDriverConfig, config: DatabaseAdapterInitConfig): Promise<InitializedDriver & {
244
+ internals: PostgresDriverInternals;
245
+ }>;
147
246
  export declare function createPostgresBootstrapper(pgConfig: PostgresDriverConfig): BackendBootstrapper;
@@ -1,5 +1,17 @@
1
1
  import { NodePgDatabase } from "drizzle-orm/node-postgres";
2
2
  import type { CollectionConfig } from "@rebasepro/types";
3
+ /**
4
+ * One statement that drops each named index — but only one that covers a
5
+ * single column that a valid, unique, non-partial btree on the same table also
6
+ * leads with. That is the UNIQUE (token_hash) constraint's index, which serves
7
+ * every `token_hash = $1` lookup; a table that somehow lacks it keeps its
8
+ * plain index, so no lookup is ever left without one.
9
+ *
10
+ * `DROP INDEX` takes the table's ACCESS EXCLUSIVE lock, on the tables every
11
+ * sign-in and token refresh touches, so it runs under the boot lock budget
12
+ * (`lock_timeout`, transaction-local to this statement — see `boot-ddl`).
13
+ */
14
+ export declare function dropDuplicateTokenHashIndexesSql(schema: string, names: readonly string[]): string;
3
15
  /**
4
16
  * Auto-create auth tables if they don't exist.
5
17
  *
@@ -1,7 +1,6 @@
1
1
  import { NodePgDatabase } from "drizzle-orm/node-postgres";
2
2
  import { type RebasePgTable } from "../types.js";
3
- import { UserRepository, TokenRepository, MfaRepository, AuthRepository, UserData, CreateUserData, RoleData, CreateRoleData, RefreshTokenInfo, RefreshTokenSession, PasswordResetTokenInfo, MagicLinkTokenInfo, UserIdentityData, ListUsersOptions, PaginatedUsersResult, MfaFactor, MfaChallengeInfo, RoleData as Role } from "@rebasepro/server";
4
- export type { Role };
3
+ import { UserRepository, TokenRepository, MfaRepository, AuthRepository, UserData, CreateUserData, RefreshTokenInfo, RefreshTokenSession, PasswordResetTokenInfo, MagicLinkTokenInfo, UserIdentityData, ListUsersOptions, PaginatedUsersResult, MfaFactor, MfaChallengeInfo, AccountAccessState, PendingEmailChange } from "@rebasepro/server";
5
4
  export interface AuthSchemaTables {
6
5
  users: RebasePgTable;
7
6
  refreshTokens: RebasePgTable;
@@ -38,6 +37,8 @@ export declare class UserService implements UserRepository {
38
37
  private db;
39
38
  private usersTable;
40
39
  private userIdentitiesTable;
40
+ /** Read by `getAccountAccessState`, to say whether one sign-in is still live. */
41
+ private refreshTokensTable;
41
42
  constructor(db: NodePgDatabase, tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>);
42
43
  private getQualifiedUsersTableName;
43
44
  /**
@@ -83,6 +84,11 @@ export declare class UserService implements UserRepository {
83
84
  updateUser(id: string, data: Partial<Omit<CreateUserData, "id">>): Promise<UserData | null>;
84
85
  deleteUser(id: string): Promise<void>;
85
86
  listUsers(): Promise<UserData[]>;
87
+ /**
88
+ * @see UserRepository.firstUserIds — an unordered `LIMIT` read of the
89
+ * primary key, not `listUsersPaginated`'s count and sort of every row.
90
+ */
91
+ firstUserIds(limit: number): Promise<string[]>;
86
92
  listUsersPaginated(options?: ListUsersOptions): Promise<PaginatedUsersResult>;
87
93
  /**
88
94
  * Update user's password hash; `null` removes the password.
@@ -100,10 +106,6 @@ export declare class UserService implements UserRepository {
100
106
  * Find user by email verification token
101
107
  */
102
108
  getUserByVerificationToken(token: string): Promise<UserData | null>;
103
- /**
104
- * Get roles for a user from database (inline TEXT[] column)
105
- */
106
- getUserRoles(uid: string): Promise<Role[]>;
107
109
  /**
108
110
  * Get role IDs for a user
109
111
  */
@@ -116,12 +118,53 @@ export declare class UserService implements UserRepository {
116
118
  * Assign a specific role to new user (appends if not present)
117
119
  */
118
120
  assignDefaultRole(uid: string, roleId: string): Promise<void>;
121
+ /**
122
+ * One read of what an access token is judged against: whether the account
123
+ * exists, its roles, and its revocation watermark. See
124
+ * `TokenRepository.getAccountAccessState`.
125
+ *
126
+ * The watermark is read only where the table declares it, as
127
+ * `RefreshTokenService.getTokensValidAfter` reads it — a table without the
128
+ * column has no watermark rather than an error on every request.
129
+ */
130
+ getAccountAccessState(uid: string, sessionId?: string): Promise<AccountAccessState | null>;
131
+ /**
132
+ * Whether the users table has `disabled_at`. `ensureAuthTablesExist` adds
133
+ * it to every auth table at boot, but the app's own drizzle object may
134
+ * predate it, so the database is asked — once — rather than the object.
135
+ */
136
+ private disabledColumn?;
137
+ private hasDisabledColumn;
138
+ /**
139
+ * Whether the users table has the address-change columns. Added at boot to
140
+ * every auth table, but the app's drizzle object never declares them, so
141
+ * the database is asked — once — as for `disabled_at`.
142
+ */
143
+ private emailChangeColumns?;
144
+ private hasEmailChangeColumns;
145
+ private requireEmailChangeColumns;
146
+ /** @see UserRepository.setPendingEmailChange */
147
+ setPendingEmailChange(uid: string, change: {
148
+ email: string;
149
+ tokenHash: string;
150
+ } | null): Promise<void>;
151
+ /** @see UserRepository.getPendingEmailChange */
152
+ getPendingEmailChange(uid: string): Promise<PendingEmailChange | null>;
153
+ /** @see UserRepository.findPendingEmailChange */
154
+ findPendingEmailChange(tokenHash: string): Promise<{
155
+ user: UserData;
156
+ change: PendingEmailChange;
157
+ } | null>;
158
+ /** @see UserRepository.applyPendingEmailChange — one write, held to the token. */
159
+ applyPendingEmailChange(uid: string, tokenHash: string): Promise<UserData | null>;
160
+ /** @see UserRepository.setUserDisabled */
161
+ setUserDisabled(uid: string, disabled: boolean): Promise<void>;
119
162
  /**
120
163
  * Get user with their roles
121
164
  */
122
165
  getUserWithRoles(uid: string): Promise<{
123
166
  user: UserData;
124
- roles: Role[];
167
+ roles: string[];
125
168
  } | null>;
126
169
  }
127
170
  export declare class RefreshTokenService {
@@ -191,6 +234,12 @@ export declare class RefreshTokenService {
191
234
  deleteAllForUser(uid: string): Promise<void>;
192
235
  listForUser(uid: string): Promise<RefreshTokenInfo[]>;
193
236
  deleteById(id: string, uid: string): Promise<void>;
237
+ /**
238
+ * Every token past its expiry, whoever holds it. `prune` clears a user's
239
+ * expired rows when that user refreshes; a session nobody comes back to
240
+ * is only ever cleared here.
241
+ */
242
+ deleteExpired(): Promise<void>;
194
243
  }
195
244
  /**
196
245
  * Password reset token service
@@ -235,6 +284,8 @@ export declare class MagicLinkTokenService {
235
284
  private getQualifiedTableName;
236
285
  createToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void>;
237
286
  findValidByHash(tokenHash: string): Promise<MagicLinkTokenInfo | null>;
287
+ /** Links and email codes past their expiry, used or not. */
288
+ deleteExpired(): Promise<void>;
238
289
  markAsUsed(tokenHash: string): Promise<void>;
239
290
  }
240
291
  /**
@@ -262,6 +313,10 @@ export declare class PostgresTokenRepository implements TokenRepository {
262
313
  findValidPasswordResetToken(tokenHash: string): Promise<PasswordResetTokenInfo | null>;
263
314
  markPasswordResetTokenUsed(tokenHash: string): Promise<void>;
264
315
  deleteAllPasswordResetTokensForUser(uid: string): Promise<void>;
316
+ /**
317
+ * @see TokenRepository.deleteExpiredTokens — reset links, magic links and
318
+ * email codes, and refresh tokens, each past its own expiry.
319
+ */
265
320
  deleteExpiredTokens(): Promise<void>;
266
321
  createMagicLinkToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void>;
267
322
  findValidMagicLinkToken(tokenHash: string): Promise<MagicLinkTokenInfo | null>;
@@ -288,23 +343,30 @@ export declare class PostgresAuthRepository implements AuthRepository {
288
343
  deleteUser(id: string): Promise<void>;
289
344
  listUsers(): Promise<UserData[]>;
290
345
  listUsersPaginated(options?: ListUsersOptions): Promise<PaginatedUsersResult>;
346
+ firstUserIds(limit: number): Promise<string[]>;
291
347
  updatePassword(id: string, passwordHash: string | null): Promise<void>;
292
348
  setEmailVerified(id: string, verified: boolean): Promise<void>;
293
349
  setVerificationToken(id: string, token: string | null): Promise<void>;
294
350
  getUserByVerificationToken(token: string): Promise<UserData | null>;
295
- getUserRoles(uid: string): Promise<RoleData[]>;
296
351
  getUserRoleIds(uid: string): Promise<string[]>;
297
352
  setUserRoles(uid: string, roleIds: string[]): Promise<void>;
298
353
  assignDefaultRole(uid: string, roleId: string): Promise<void>;
299
354
  getUserWithRoles(uid: string): Promise<{
300
355
  user: UserData;
301
- roles: RoleData[];
356
+ roles: string[];
357
+ } | null>;
358
+ getAccountAccessState(uid: string, sessionId?: string): Promise<AccountAccessState | null>;
359
+ setUserDisabled(uid: string, disabled: boolean): Promise<void>;
360
+ setPendingEmailChange(uid: string, change: {
361
+ email: string;
362
+ tokenHash: string;
363
+ } | null): Promise<void>;
364
+ getPendingEmailChange(uid: string): Promise<PendingEmailChange | null>;
365
+ findPendingEmailChange(tokenHash: string): Promise<{
366
+ user: UserData;
367
+ change: PendingEmailChange;
302
368
  } | null>;
303
- getRoleById(id: string): Promise<RoleData | null>;
304
- listRoles(): Promise<RoleData[]>;
305
- createRole(_data: CreateRoleData): Promise<RoleData>;
306
- updateRole(id: string, data: Partial<Omit<RoleData, "id">>): Promise<RoleData | null>;
307
- deleteRole(_id: string): Promise<void>;
369
+ applyPendingEmailChange(uid: string, tokenHash: string): Promise<UserData | null>;
308
370
  createRefreshToken(uid: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string, session?: RefreshTokenSession): Promise<boolean>;
309
371
  markRefreshTokenRotated(tokenHash: string): Promise<void>;
310
372
  revokeRefreshTokenSession(sessionId: string): Promise<void>;
@@ -383,6 +445,8 @@ export declare class MfaService implements MfaRepository {
383
445
  * share a single increment.
384
446
  */
385
447
  recordMfaChallengeAttempt(challengeId: string): Promise<number>;
448
+ /** Challenges past their five minutes, answered or not. */
449
+ deleteExpiredChallenges(): Promise<void>;
386
450
  verifyMfaChallenge(challengeId: string): Promise<void>;
387
451
  createRecoveryCodes(uid: string, codeHashes: string[]): Promise<void>;
388
452
  useRecoveryCode(uid: string, codeHash: string): Promise<boolean>;
@@ -62,6 +62,22 @@ var AUTH_USERS_COLUMNS = [
62
62
  column: "tokens_valid_after",
63
63
  type: "TIMESTAMP WITH TIME ZONE"
64
64
  },
65
+ {
66
+ column: "disabled_at",
67
+ type: "TIMESTAMP WITH TIME ZONE"
68
+ },
69
+ {
70
+ column: "pending_email",
71
+ type: "TEXT"
72
+ },
73
+ {
74
+ column: "email_change_token",
75
+ type: "TEXT"
76
+ },
77
+ {
78
+ column: "email_change_sent_at",
79
+ type: "TIMESTAMP WITH TIME ZONE"
80
+ },
65
81
  {
66
82
  column: "created_at",
67
83
  type: "TIMESTAMP WITH TIME ZONE",
@@ -113,4 +129,4 @@ function isAuthCollection(collection) {
113
129
  //#endregion
114
130
  export { isAuthCollection as i, authUsersColumnDefinition as n, authUsersColumnSql as r, AUTH_USERS_COLUMNS as t };
115
131
 
116
- //# sourceMappingURL=auth-users-columns-D2LBFrMH.js.map
132
+ //# sourceMappingURL=auth-users-columns-C72EMoDJ.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth-users-columns-D2LBFrMH.js","names":[],"sources":["../src/schema/auth-users-columns.ts"],"sourcesContent":["/**\n * The one description of what an auth user table's columns must be.\n *\n * ## Why this exists\n *\n * Three different code paths create `rebase.users`, and before this module they\n * disagreed about it:\n *\n * | column | `db push` (generator) | `ensureAuthTablesExist` | `ensureCollectionTables` |\n * |------------------|-----------------------|-------------------------|--------------------------|\n * | `email` | `TEXT UNIQUE NOT NULL`| `TEXT NOT NULL` + CHECK | `TEXT` (nullable) |\n * | `roles` | `TEXT[]` | `NOT NULL DEFAULT '{}'` | nullable, no default |\n * | `email_verified` | `BOOLEAN` | `NOT NULL DEFAULT FALSE`| nullable |\n * | `created_at` | `DEFAULT now()` | `NOT NULL DEFAULT NOW()`| no default |\n *\n * They are all `CREATE TABLE IF NOT EXISTS` / `ADD COLUMN IF NOT EXISTS`, so\n * whichever ran first decided the table and the other two silently no-op'd.\n * Boot order therefore chose the constraints: a managed deploy (collection\n * tables first) got a users table whose `email` was nullable and whose\n * `email_verified` had no default, while the same project pushed from a\n * checkout got the strict one. In the same table, `is_anonymous` came out\n * `NOT NULL DEFAULT false` — because *that* column existed in only one of the\n * three lists, so its owner's definition won by default.\n *\n * A second consequence was drift in the other direction. The scaffold's\n * `users.ts` describes 12 columns; auth needs 14. `db push` is declarative and\n * builds its desired state from the collection alone, so the two columns only\n * auth knows about (`is_anonymous`, `tokens_valid_after`) read as unmanaged\n * drift, and a push run after the server had booted once planned to DROP them.\n *\n * Both problems are the same problem: no single place said what this table is.\n * This is that place. Every creator asks here first and only then falls back to\n * the collection's own property definitions, so a column auth owns has one\n * definition regardless of who gets there first, and a column the developer\n * added to their users collection still behaves like any other field.\n *\n * ## What belongs here\n *\n * Only columns the auth services read or write. A developer's own additions to\n * their users collection (`bio`, `stripe_customer_id`) are ordinary columns and\n * must NOT be listed — they are generated from the collection like every other\n * field, and listing them here would freeze a user's schema.\n *\n * `id` is deliberately absent: its type comes from the collection's id property\n * (uuid / increment / text) and each creator already derives it.\n *\n * Keep in step with {@link AUTH_SCHEMA_VERSION} when a change here makes an\n * older runtime unable to work against a migrated table.\n */\n\n/**\n * A column auth owns.\n *\n * Structured rather than one SQL string because the same facts are needed in\n * three grammars — a `CREATE TABLE` column list, an `ADD COLUMN IF NOT EXISTS`,\n * and a pair of `ALTER COLUMN … SET DEFAULT` / `SET NOT NULL` reconciles for a\n * table that already exists with the wrong shape. Parsing a string back apart\n * for the third of those is how the copies drifted in the first place.\n */\nexport interface AuthUsersColumn {\n /** Physical column name. */\n column: string;\n /** Postgres type. */\n type: string;\n /** Default expression, verbatim, or absent for no default. */\n default?: string;\n /** Whether the column is NOT NULL. */\n notNull?: boolean;\n}\n\n/**\n * `email` is NOT NULL on purpose, and the anonymous sign-in route depends on it\n * — it synthesizes `anon_<32 hex>@anonymous.local` rather than inserting NULL.\n * The 320-char bound (RFC 5321) is a CHECK rather than a `VARCHAR(n)`, added\n * separately by `ensureAuthTablesExist` so it can be `NOT VALID` on an adopted\n * table that already holds a longer row.\n */\nexport const AUTH_USERS_COLUMNS: readonly AuthUsersColumn[] = [\n { column: \"email\", type: \"TEXT\", notNull: true },\n { column: \"display_name\", type: \"TEXT\" },\n { column: \"photo_url\", type: \"TEXT\" },\n { column: \"roles\", type: \"TEXT[]\", default: \"'{}'\", notNull: true },\n { column: \"password_hash\", type: \"TEXT\" },\n { column: \"email_verified\", type: \"BOOLEAN\", default: \"FALSE\", notNull: true },\n { column: \"email_verification_token\", type: \"TEXT\" },\n { column: \"email_verification_sent_at\", type: \"TIMESTAMP WITH TIME ZONE\" },\n { column: \"is_anonymous\", type: \"BOOLEAN\", default: \"FALSE\", notNull: true },\n { column: \"metadata\", type: \"JSONB\", default: \"'{}'\", notNull: true },\n { column: \"tokens_valid_after\", type: \"TIMESTAMP WITH TIME ZONE\" },\n { column: \"created_at\", type: \"TIMESTAMP WITH TIME ZONE\", default: \"NOW()\", notNull: true },\n { column: \"updated_at\", type: \"TIMESTAMP WITH TIME ZONE\", default: \"NOW()\", notNull: true }\n];\n\nconst BY_COLUMN = new Map(AUTH_USERS_COLUMNS.map(c => [c.column, c]));\n\n/** Type + inline constraints, as they appear after the column name. */\nexport function authUsersColumnSql(spec: AuthUsersColumn): string {\n return [\n spec.type,\n spec.default !== undefined ? `DEFAULT ${spec.default}` : \"\",\n spec.notNull ? \"NOT NULL\" : \"\"\n ].filter(Boolean).join(\" \");\n}\n\n/**\n * The auth-owned definition for a physical column name, or `undefined` when\n * auth does not own it.\n *\n * Callers pass the RESOLVED column name (after `columnName` mapping), because\n * that is the only name the three creators agree on: the scaffold's users\n * collection spells the property `displayName` and the column `display_name`.\n */\nexport function authUsersColumnDefinition(column: string): string | undefined {\n const spec = BY_COLUMN.get(column);\n return spec ? authUsersColumnSql(spec) : undefined;\n}\n\n/**\n * Whether a collection is an auth collection, i.e. whether the definitions in\n * this module apply to its table at all.\n *\n * Duplicated in shape from `@rebasepro/common`'s policy defaults on purpose:\n * that one takes a `CollectionConfig`, this one is called from DDL code paths\n * that hold looser objects, and both spellings must accept `auth: true` as well\n * as `auth: { enabled: true }`.\n */\nexport function isAuthCollection(collection: unknown): boolean {\n const auth = (collection as { auth?: unknown } | undefined)?.auth;\n if (auth === true) return true;\n return typeof auth === \"object\" && auth !== null && (auth as { enabled?: unknown }).enabled === true;\n}\n"],"mappings":";;;;;;;;;;AA6EA,IAAa,qBAAiD;CAC1D;EAAE,QAAQ;EAAS,MAAM;EAAQ,SAAS;CAAK;CAC/C;EAAE,QAAQ;EAAgB,MAAM;CAAO;CACvC;EAAE,QAAQ;EAAa,MAAM;CAAO;CACpC;EAAE,QAAQ;EAAS,MAAM;EAAU,SAAS;EAAQ,SAAS;CAAK;CAClE;EAAE,QAAQ;EAAiB,MAAM;CAAO;CACxC;EAAE,QAAQ;EAAkB,MAAM;EAAW,SAAS;EAAS,SAAS;CAAK;CAC7E;EAAE,QAAQ;EAA4B,MAAM;CAAO;CACnD;EAAE,QAAQ;EAA8B,MAAM;CAA2B;CACzE;EAAE,QAAQ;EAAgB,MAAM;EAAW,SAAS;EAAS,SAAS;CAAK;CAC3E;EAAE,QAAQ;EAAY,MAAM;EAAS,SAAS;EAAQ,SAAS;CAAK;CACpE;EAAE,QAAQ;EAAsB,MAAM;CAA2B;CACjE;EAAE,QAAQ;EAAc,MAAM;EAA4B,SAAS;EAAS,SAAS;CAAK;CAC1F;EAAE,QAAQ;EAAc,MAAM;EAA4B,SAAS;EAAS,SAAS;CAAK;AAC9F;AAEA,IAAM,YAAY,IAAI,IAAI,mBAAmB,KAAI,MAAK,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC;;AAGpE,SAAgB,mBAAmB,MAA+B;CAC9D,OAAO;EACH,KAAK;EACL,KAAK,YAAY,KAAA,IAAY,WAAW,KAAK,YAAY;EACzD,KAAK,UAAU,aAAa;CAChC,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG;AAC9B;;;;;;;;;AAUA,SAAgB,0BAA0B,QAAoC;CAC1E,MAAM,OAAO,UAAU,IAAI,MAAM;CACjC,OAAO,OAAO,mBAAmB,IAAI,IAAI,KAAA;AAC7C;;;;;;;;;;AAWA,SAAgB,iBAAiB,YAA8B;CAC3D,MAAM,OAAQ,YAA+C;CAC7D,IAAI,SAAS,MAAM,OAAO;CAC1B,OAAO,OAAO,SAAS,YAAY,SAAS,QAAS,KAA+B,YAAY;AACpG"}
1
+ {"version":3,"file":"auth-users-columns-C72EMoDJ.js","names":[],"sources":["../src/schema/auth-users-columns.ts"],"sourcesContent":["/**\n * The one description of what an auth user table's columns must be.\n *\n * ## Why this exists\n *\n * Three different code paths create `rebase.users`, and before this module they\n * disagreed about it:\n *\n * | column | `db push` (generator) | `ensureAuthTablesExist` | `ensureCollectionTables` |\n * |------------------|-----------------------|-------------------------|--------------------------|\n * | `email` | `TEXT UNIQUE NOT NULL`| `TEXT NOT NULL` + CHECK | `TEXT` (nullable) |\n * | `roles` | `TEXT[]` | `NOT NULL DEFAULT '{}'` | nullable, no default |\n * | `email_verified` | `BOOLEAN` | `NOT NULL DEFAULT FALSE`| nullable |\n * | `created_at` | `DEFAULT now()` | `NOT NULL DEFAULT NOW()`| no default |\n *\n * They are all `CREATE TABLE IF NOT EXISTS` / `ADD COLUMN IF NOT EXISTS`, so\n * whichever ran first decided the table and the other two silently no-op'd.\n * Boot order therefore chose the constraints: a managed deploy (collection\n * tables first) got a users table whose `email` was nullable and whose\n * `email_verified` had no default, while the same project pushed from a\n * checkout got the strict one. In the same table, `is_anonymous` came out\n * `NOT NULL DEFAULT false` — because *that* column existed in only one of the\n * three lists, so its owner's definition won by default.\n *\n * A second consequence was drift in the other direction. The scaffold's\n * `users.ts` describes 12 columns; auth needs 14. `db push` is declarative and\n * builds its desired state from the collection alone, so the two columns only\n * auth knows about (`is_anonymous`, `tokens_valid_after`) read as unmanaged\n * drift, and a push run after the server had booted once planned to DROP them.\n *\n * Both problems are the same problem: no single place said what this table is.\n * This is that place. Every creator asks here first and only then falls back to\n * the collection's own property definitions, so a column auth owns has one\n * definition regardless of who gets there first, and a column the developer\n * added to their users collection still behaves like any other field.\n *\n * ## What belongs here\n *\n * Only columns the auth services read or write. A developer's own additions to\n * their users collection (`bio`, `stripe_customer_id`) are ordinary columns and\n * must NOT be listed — they are generated from the collection like every other\n * field, and listing them here would freeze a user's schema.\n *\n * `id` is deliberately absent: its type comes from the collection's id property\n * (uuid / increment / text) and each creator already derives it.\n *\n * Keep in step with {@link AUTH_SCHEMA_VERSION} when a change here makes an\n * older runtime unable to work against a migrated table.\n */\n\n/**\n * A column auth owns.\n *\n * Structured rather than one SQL string because the same facts are needed in\n * three grammars — a `CREATE TABLE` column list, an `ADD COLUMN IF NOT EXISTS`,\n * and a pair of `ALTER COLUMN … SET DEFAULT` / `SET NOT NULL` reconciles for a\n * table that already exists with the wrong shape. Parsing a string back apart\n * for the third of those is how the copies drifted in the first place.\n */\nexport interface AuthUsersColumn {\n /** Physical column name. */\n column: string;\n /** Postgres type. */\n type: string;\n /** Default expression, verbatim, or absent for no default. */\n default?: string;\n /** Whether the column is NOT NULL. */\n notNull?: boolean;\n}\n\n/**\n * `email` is NOT NULL on purpose, and the anonymous sign-in route depends on it\n * — it synthesizes `anon_<32 hex>@anonymous.local` rather than inserting NULL.\n * The 320-char bound (RFC 5321) is a CHECK rather than a `VARCHAR(n)`, added\n * separately by `ensureAuthTablesExist` so it can be `NOT VALID` on an adopted\n * table that already holds a longer row.\n */\nexport const AUTH_USERS_COLUMNS: readonly AuthUsersColumn[] = [\n { column: \"email\", type: \"TEXT\", notNull: true },\n { column: \"display_name\", type: \"TEXT\" },\n { column: \"photo_url\", type: \"TEXT\" },\n { column: \"roles\", type: \"TEXT[]\", default: \"'{}'\", notNull: true },\n { column: \"password_hash\", type: \"TEXT\" },\n { column: \"email_verified\", type: \"BOOLEAN\", default: \"FALSE\", notNull: true },\n { column: \"email_verification_token\", type: \"TEXT\" },\n { column: \"email_verification_sent_at\", type: \"TIMESTAMP WITH TIME ZONE\" },\n { column: \"is_anonymous\", type: \"BOOLEAN\", default: \"FALSE\", notNull: true },\n { column: \"metadata\", type: \"JSONB\", default: \"'{}'\", notNull: true },\n { column: \"tokens_valid_after\", type: \"TIMESTAMP WITH TIME ZONE\" },\n // When an administrator switched the account off; NULL while it is on.\n { column: \"disabled_at\", type: \"TIMESTAMP WITH TIME ZONE\" },\n // A self-service address change waiting for its link: the address, the\n // hash of the token mailed to it, and when (it lives 24 hours).\n { column: \"pending_email\", type: \"TEXT\" },\n { column: \"email_change_token\", type: \"TEXT\" },\n { column: \"email_change_sent_at\", type: \"TIMESTAMP WITH TIME ZONE\" },\n { column: \"created_at\", type: \"TIMESTAMP WITH TIME ZONE\", default: \"NOW()\", notNull: true },\n { column: \"updated_at\", type: \"TIMESTAMP WITH TIME ZONE\", default: \"NOW()\", notNull: true }\n];\n\nconst BY_COLUMN = new Map(AUTH_USERS_COLUMNS.map(c => [c.column, c]));\n\n/** Type + inline constraints, as they appear after the column name. */\nexport function authUsersColumnSql(spec: AuthUsersColumn): string {\n return [\n spec.type,\n spec.default !== undefined ? `DEFAULT ${spec.default}` : \"\",\n spec.notNull ? \"NOT NULL\" : \"\"\n ].filter(Boolean).join(\" \");\n}\n\n/**\n * The auth-owned definition for a physical column name, or `undefined` when\n * auth does not own it.\n *\n * Callers pass the RESOLVED column name (after `columnName` mapping), because\n * that is the only name the three creators agree on: the scaffold's users\n * collection spells the property `displayName` and the column `display_name`.\n */\nexport function authUsersColumnDefinition(column: string): string | undefined {\n const spec = BY_COLUMN.get(column);\n return spec ? authUsersColumnSql(spec) : undefined;\n}\n\n/**\n * Whether a collection is an auth collection, i.e. whether the definitions in\n * this module apply to its table at all.\n *\n * Duplicated in shape from `@rebasepro/common`'s policy defaults on purpose:\n * that one takes a `CollectionConfig`, this one is called from DDL code paths\n * that hold looser objects, and both spellings must accept `auth: true` as well\n * as `auth: { enabled: true }`.\n */\nexport function isAuthCollection(collection: unknown): boolean {\n const auth = (collection as { auth?: unknown } | undefined)?.auth;\n if (auth === true) return true;\n return typeof auth === \"object\" && auth !== null && (auth as { enabled?: unknown }).enabled === true;\n}\n"],"mappings":";;;;;;;;;;AA6EA,IAAa,qBAAiD;CAC1D;EAAE,QAAQ;EAAS,MAAM;EAAQ,SAAS;CAAK;CAC/C;EAAE,QAAQ;EAAgB,MAAM;CAAO;CACvC;EAAE,QAAQ;EAAa,MAAM;CAAO;CACpC;EAAE,QAAQ;EAAS,MAAM;EAAU,SAAS;EAAQ,SAAS;CAAK;CAClE;EAAE,QAAQ;EAAiB,MAAM;CAAO;CACxC;EAAE,QAAQ;EAAkB,MAAM;EAAW,SAAS;EAAS,SAAS;CAAK;CAC7E;EAAE,QAAQ;EAA4B,MAAM;CAAO;CACnD;EAAE,QAAQ;EAA8B,MAAM;CAA2B;CACzE;EAAE,QAAQ;EAAgB,MAAM;EAAW,SAAS;EAAS,SAAS;CAAK;CAC3E;EAAE,QAAQ;EAAY,MAAM;EAAS,SAAS;EAAQ,SAAS;CAAK;CACpE;EAAE,QAAQ;EAAsB,MAAM;CAA2B;CAEjE;EAAE,QAAQ;EAAe,MAAM;CAA2B;CAG1D;EAAE,QAAQ;EAAiB,MAAM;CAAO;CACxC;EAAE,QAAQ;EAAsB,MAAM;CAAO;CAC7C;EAAE,QAAQ;EAAwB,MAAM;CAA2B;CACnE;EAAE,QAAQ;EAAc,MAAM;EAA4B,SAAS;EAAS,SAAS;CAAK;CAC1F;EAAE,QAAQ;EAAc,MAAM;EAA4B,SAAS;EAAS,SAAS;CAAK;AAC9F;AAEA,IAAM,YAAY,IAAI,IAAI,mBAAmB,KAAI,MAAK,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC;;AAGpE,SAAgB,mBAAmB,MAA+B;CAC9D,OAAO;EACH,KAAK;EACL,KAAK,YAAY,KAAA,IAAY,WAAW,KAAK,YAAY;EACzD,KAAK,UAAU,aAAa;CAChC,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG;AAC9B;;;;;;;;;AAUA,SAAgB,0BAA0B,QAAoC;CAC1E,MAAM,OAAO,UAAU,IAAI,MAAM;CACjC,OAAO,OAAO,mBAAmB,IAAI,IAAI,KAAA;AAC7C;;;;;;;;;;AAWA,SAAgB,iBAAiB,YAA8B;CAC3D,MAAM,OAAQ,YAA+C;CAC7D,IAAI,SAAS,MAAM,OAAO;CAC1B,OAAO,OAAO,SAAS,YAAY,SAAS,QAAS,KAA+B,YAAY;AACpG"}
@@ -3,9 +3,82 @@
3
3
  * `rebase db backups list`. Kept out of the main `cli.ts` dispatcher so the
4
4
  * backup surface stays self-contained.
5
5
  */
6
- import arg from "arg";
6
+ import type arg from "arg";
7
+ import type { StorageController } from "@rebasepro/server";
8
+ import { BackupDestination } from "./pg-tools.js";
9
+ /**
10
+ * The controller for an object-storage destination, or `null` for a local one.
11
+ *
12
+ * Built by `backupStorageFromEnv` from `@rebasepro/server` — the function the
13
+ * Backups panel's routes use — so the CLI and the panel read a bucket the same
14
+ * way. This file had its own copy, and the two had already drifted once
15
+ * (`S3_FORCE_PATH_STYLE` read as `=== "true"`, so a MinIO backup addressed the
16
+ * bucket as a host).
17
+ *
18
+ * Exported for its test.
19
+ */
20
+ export declare function resolveStorageForDestination(dest: BackupDestination, env: Record<string, string | undefined>): Promise<StorageController | null>;
21
+ /**
22
+ * The directory `rebase` was run from, which `rebase` puts here for the driver.
23
+ *
24
+ * The driver runs in the project's `backend/`, so a relative path resolved
25
+ * against its own working directory named a different place from the one the
26
+ * user typed it in: `rebase db backup --out ./backups` wrote to `<root>/backups`
27
+ * (the CLI resolves explicit paths itself) while `rebase db backups list`
28
+ * looked in `<root>/backend/backups`. Spelled the same in
29
+ * `packages/cli/src/commands/db.ts`.
30
+ */
31
+ export declare const INVOCATION_CWD_ENV = "REBASE_INVOCATION_CWD";
32
+ /** Where a relative path the user typed is relative to: where they ran `rebase`. */
33
+ export declare function invocationDir(env?: Readonly<Record<string, string | undefined>>): string;
34
+ /**
35
+ * Where `db backup` writes and `db backups list` reads, by one rule.
36
+ *
37
+ * - `--out` is relative to where `rebase` was run, like any path typed on a
38
+ * command line.
39
+ * - `BACKUP_DESTINATION` is relative to the backend directory, because the
40
+ * server reads the same variable there: the Backups panel and the scheduled
41
+ * backup have to find what the CLI wrote.
42
+ * - With neither, `./backups` where `rebase` was run, as the help says.
43
+ *
44
+ * An `s3://` or `gs://` destination is returned as given.
45
+ */
46
+ export declare function resolveBackupLocation(opts: {
47
+ flag?: string;
48
+ configured?: string;
49
+ invocationDir: string;
50
+ backendDir: string;
51
+ }): string;
52
+ /**
53
+ * Parse a `rebase db backup …` line. An undeclared flag is refused (see
54
+ * `parseCommandFlags`), and so is a positional: `rebase db backup ./backups`
55
+ * means `--out ./backups`, and taken as nothing it wrote to the default
56
+ * directory.
57
+ */
58
+ export declare function parseBackupLine(rawArgs: string[]): arg.Result<{
59
+ "--database-url": StringConstructor;
60
+ "--docker": BooleanConstructor;
61
+ "--debug": BooleanConstructor;
62
+ "--help": BooleanConstructor;
63
+ "-h": string;
64
+ } & {
65
+ "--out": StringConstructor;
66
+ "--output": string;
67
+ "--exclude-schema": [StringConstructor];
68
+ "--no-owner": BooleanConstructor;
69
+ "--enable-row-security": BooleanConstructor;
70
+ "--row-security-role": StringConstructor;
71
+ "-o": string;
72
+ }>;
7
73
  export declare function backupCommand(rawArgs: string[]): Promise<void>;
8
- /** Parse a `rebase db restore …` line; the first positional is the backup. */
74
+ /**
75
+ * Parse a `rebase db restore …` line; the one positional is the backup.
76
+ *
77
+ * Strict, because this is the command where a typo costs most: parsed
78
+ * permissively, `--targt-db x` became a second positional, the target fell
79
+ * back to DATABASE_URL's own database, and the restore ran against the live
80
+ * one.
81
+ */
9
82
  export declare function parseRestoreLine(rawArgs: string[]): arg.Result<{
10
83
  "--database-url": StringConstructor;
11
84
  "--docker": BooleanConstructor;
@@ -1,4 +1,5 @@
1
1
  import type { StorageController } from "@rebasepro/server";
2
+ import { resolvePgBinary } from "./pg-binary.js";
2
3
  import { BackupDestination, type RowSecurityIdentity, VersionCompatibility } from "./pg-tools.js";
3
4
  import { BackupObject, RetentionOptions } from "./retention.js";
4
5
  /**
@@ -11,8 +12,7 @@ export declare class BackupToolError extends Error {
11
12
  readonly hint?: string | undefined;
12
13
  constructor(message: string, hint?: string | undefined);
13
14
  }
14
- /** Locate `pg_dump` / `pg_restore` / `pg_dumpall`, honouring an env override. */
15
- export declare function resolvePgBinary(tool: "pg_dump" | "pg_restore" | "pg_dumpall", env?: Record<string, string | undefined>): string | null;
15
+ export { resolvePgBinary };
16
16
  /** Run `<bin> --version` and extract the major version. */
17
17
  export declare function detectToolMajor(bin: string): Promise<number | null>;
18
18
  /** Query the server for its major version via `server_version_num`. */
@@ -0,0 +1,5 @@
1
+ export type PgTool = "pg_dump" | "pg_restore" | "pg_dumpall";
2
+ /** The variable that points at `tool` when it is not on the PATH. */
3
+ export declare function pgToolVariable(tool: PgTool): string;
4
+ /** Locate `pg_dump` / `pg_restore` / `pg_dumpall`, honouring an env override. */
5
+ export declare function resolvePgBinary(tool: PgTool, env?: Record<string, string | undefined>): string | null;