@rebasepro/server-postgres 0.23.0 → 0.24.1-canary.g56af4a4
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.
- package/dist/{BranchService-DGPL6G_C.js → BranchService-Bsn2wfaj.js} +36 -18
- package/dist/BranchService-Bsn2wfaj.js.map +1 -0
- package/dist/PostgresBackendDriver.d.ts +166 -13
- package/dist/auth/ensure-tables.d.ts +12 -0
- package/dist/auth/services.d.ts +78 -14
- package/dist/{auth-users-columns-D2LBFrMH.js → auth-users-columns-C72EMoDJ.js} +17 -1
- package/dist/{auth-users-columns-D2LBFrMH.js.map → auth-users-columns-C72EMoDJ.js.map} +1 -1
- package/dist/backup/backup-cli.d.ts +14 -0
- package/dist/backup/backup-service.d.ts +2 -2
- package/dist/backup/pg-binary.d.ts +5 -0
- package/dist/{backup-cli-24v4OlSp.js → backup-cli-aUfRv16h.js} +37 -29
- package/dist/backup-cli-aUfRv16h.js.map +1 -0
- package/dist/{backup-service-HQ9GC4tN.js → backup-service-BTBB7RXn.js} +60 -28
- package/dist/backup-service-BTBB7RXn.js.map +1 -0
- package/dist/boot-ddl-DVpsFN6r.js +105 -0
- package/dist/boot-ddl-DVpsFN6r.js.map +1 -0
- package/dist/cli-errors-CaI4ntyD.js +1919 -0
- package/dist/cli-errors-CaI4ntyD.js.map +1 -0
- package/dist/cli-errors.d.ts +93 -5
- package/dist/{cli-flags-BglvpjHv.js → cli-flags-B-Dawocl.js} +5 -2
- package/dist/cli-flags-B-Dawocl.js.map +1 -0
- package/dist/cli-helpers.d.ts +52 -12
- package/dist/cli-output.d.ts +16 -28
- package/dist/cli-push.d.ts +87 -0
- package/dist/cli.js +625 -190
- package/dist/cli.js.map +1 -1
- package/dist/{column-plan-helpers-DF-8dTVa.js → column-plan-helpers-drZIsSNf.js} +42 -26
- package/dist/column-plan-helpers-drZIsSNf.js.map +1 -0
- package/dist/{databasePoolManager-Bj5FbeAs.js → connection-BPoNILnD.js} +19 -110
- package/dist/connection-BPoNILnD.js.map +1 -0
- package/dist/connection.d.ts +9 -1
- package/dist/data-transformer.d.ts +0 -8
- package/dist/databasePoolManager-BkEy2w18.js +119 -0
- package/dist/databasePoolManager-BkEy2w18.js.map +1 -0
- package/dist/deferred-index-build-DP1_SNFo.js +1051 -0
- package/dist/deferred-index-build-DP1_SNFo.js.map +1 -0
- package/dist/{doctor-Cb2thZ8s.js → doctor-C4WiftHm.js} +279 -87
- package/dist/doctor-C4WiftHm.js.map +1 -0
- package/dist/ensure-collection-policies-CXpbeNag.js +541 -0
- package/dist/ensure-collection-policies-CXpbeNag.js.map +1 -0
- package/dist/ensure-collection-tables-BhsU6qUE.js +532 -0
- package/dist/ensure-collection-tables-BhsU6qUE.js.map +1 -0
- package/dist/{ensure-tables-Dhn9KM3B.js → ensure-tables-CHsgyLqg.js} +178 -75
- package/dist/ensure-tables-CHsgyLqg.js.map +1 -0
- package/dist/enum-labels-kept-D8fI1MoX.js +56 -0
- package/dist/enum-labels-kept-D8fI1MoX.js.map +1 -0
- package/dist/generate-drizzle-schema-Bz52jH2h.js +57 -0
- package/dist/generate-drizzle-schema-Bz52jH2h.js.map +1 -0
- package/dist/{generate-drizzle-schema-logic-BfzK7UQd.js → generate-drizzle-schema-logic-B74WN37S.js} +28 -10
- package/dist/generate-drizzle-schema-logic-B74WN37S.js.map +1 -0
- package/dist/generate-files-CdEckX6c.js +199 -0
- package/dist/generate-files-CdEckX6c.js.map +1 -0
- package/dist/generate-postgres-ddl-logic-B64O2oZF.js +152 -0
- package/dist/generate-postgres-ddl-logic-B64O2oZF.js.map +1 -0
- package/dist/generated-sql.d.ts +34 -0
- package/dist/history/HistoryService.d.ts +38 -6
- package/dist/history/ensure-history-table.d.ts +42 -4
- package/dist/index.es.js +5557 -7612
- package/dist/index.es.js.map +1 -1
- package/dist/{introspect-db-logic-WMuAfxvw.js → introspect-db-logic-C-Di8dA9.js} +562 -350
- package/dist/introspect-db-logic-C-Di8dA9.js.map +1 -0
- package/dist/introspect-db-queries-D6kAqReI.js +339 -0
- package/dist/introspect-db-queries-D6kAqReI.js.map +1 -0
- package/dist/pg-error-utils-vJezlXdI.js +388 -0
- package/dist/pg-error-utils-vJezlXdI.js.map +1 -0
- package/dist/{plan-schema-C0fxM8dY.js → plan-schema-B1YF_tYq.js} +278 -533
- package/dist/plan-schema-B1YF_tYq.js.map +1 -0
- package/dist/{policy-drift-DljYdrpW.js → policy-drift-Dm8i-s1J.js} +24 -7
- package/dist/policy-drift-Dm8i-s1J.js.map +1 -0
- package/dist/push-index-prebuild-Db4AQF4i.js +45 -0
- package/dist/push-index-prebuild-Db4AQF4i.js.map +1 -0
- package/dist/push-legacy-renames-BK6ilQq2.js +34 -0
- package/dist/push-legacy-renames-BK6ilQq2.js.map +1 -0
- package/dist/{generate-postgres-ddl-logic-Bt2d2mRH.js → render-ddl-CtC3y9Ol.js} +17 -151
- package/dist/render-ddl-CtC3y9Ol.js.map +1 -0
- package/dist/{rls-bootstrap-sql-B8EclDyM.js → rls-bootstrap-sql-RQUljnJ1.js} +125 -1046
- package/dist/rls-bootstrap-sql-RQUljnJ1.js.map +1 -0
- package/dist/{rls-enforcement-C6Xk0lA6.js → rls-enforcement-DvzqlaL1.js} +137 -17
- package/dist/rls-enforcement-DvzqlaL1.js.map +1 -0
- package/dist/schema/auth-schema.d.ts +172 -2
- package/dist/schema/boot-ddl.d.ts +47 -0
- package/dist/schema/classify-change.d.ts +29 -1
- package/dist/schema/collection-index.d.ts +45 -0
- package/dist/schema/column-plan-helpers.d.ts +38 -21
- package/dist/schema/deferred-index-build.d.ts +131 -0
- package/dist/schema/destructive-sql.d.ts +27 -1
- package/dist/schema/doctor-cli.js +110 -6
- package/dist/schema/doctor-cli.js.map +1 -1
- package/dist/schema/doctor-search-scale.d.ts +45 -0
- package/dist/schema/doctor.d.ts +53 -1
- package/dist/schema/ensure-collection-policies.d.ts +95 -35
- package/dist/schema/ensure-collection-tables.d.ts +56 -3
- package/dist/schema/enum-labels-kept.d.ts +26 -0
- package/dist/schema/generate-drizzle-schema.js +1 -1
- package/dist/schema/generate-files.d.ts +44 -0
- package/dist/schema/generate-next-step.d.ts +0 -13
- package/dist/schema/generate-postgres-ddl-logic.d.ts +5 -5
- package/dist/schema/generate-postgres-ddl.js +10 -81
- package/dist/schema/generate-postgres-ddl.js.map +1 -1
- package/dist/schema/generate-schema-commit.d.ts +12 -0
- package/dist/schema/introspect-db-logic.d.ts +43 -0
- package/dist/schema/introspect-db-queries.d.ts +12 -2
- package/dist/schema/introspect-db-search.d.ts +22 -0
- package/dist/schema/introspect-db-storage.d.ts +83 -0
- package/dist/schema/introspect-db-structure.d.ts +10 -0
- package/dist/schema/introspect-db.js +17 -6
- package/dist/schema/introspect-db.js.map +1 -1
- package/dist/schema/migration-post-process.d.ts +34 -0
- package/dist/schema/plan/diff-plan.d.ts +127 -1
- package/dist/schema/plan/plan-schema.d.ts +16 -8
- package/dist/schema/plan/render-ddl.d.ts +6 -0
- package/dist/schema/plan/types.d.ts +45 -11
- package/dist/schema/push-index-prebuild.d.ts +25 -0
- package/dist/schema/push-legacy-renames.d.ts +20 -0
- package/dist/schema/rls-bootstrap-sql.d.ts +15 -0
- package/dist/search-column-BM-GV6vH.js +442 -0
- package/dist/search-column-BM-GV6vH.js.map +1 -0
- package/dist/security/policy-drift.d.ts +1 -1
- package/dist/security/rls-enforcement.d.ts +39 -4
- package/dist/services/BranchService.d.ts +22 -1
- package/dist/services/FetchService.d.ts +63 -12
- package/dist/services/PersistService.d.ts +15 -0
- package/dist/services/RelationService.d.ts +106 -3
- package/dist/services/cdc/CdcListener.d.ts +26 -10
- package/dist/services/cdc/identity-columns.d.ts +19 -0
- package/dist/services/cdc/trigger-cdc.d.ts +180 -18
- package/dist/services/channel-bus/PostgresChannelBus.d.ts +12 -7
- package/dist/services/channel-history.d.ts +32 -5
- package/dist/services/collection-helpers.d.ts +26 -1
- package/dist/services/field-viewer.d.ts +15 -1
- package/dist/services/pg-notify-listener.d.ts +95 -6
- package/dist/services/realtimeService.d.ts +467 -53
- package/dist/services/row-pipeline.d.ts +16 -9
- package/dist/services/socket-liveness.d.ts +85 -0
- package/dist/services/soft-delete.d.ts +12 -0
- package/dist/services/sql-script.d.ts +275 -0
- package/dist/services/vector-iterative-scan.d.ts +40 -0
- package/dist/services/write-depth.d.ts +22 -0
- package/dist/services/write-transaction-scope.d.ts +15 -3
- package/dist/socket-liveness-D3UD5_BW.js +7570 -0
- package/dist/socket-liveness-D3UD5_BW.js.map +1 -0
- package/dist/socket-opt-ins.d.ts +64 -0
- package/dist/source-DBDbBTL4.js +811 -0
- package/dist/source-DBDbBTL4.js.map +1 -0
- package/dist/tsx-loader.d.ts +14 -0
- package/dist/utils/drizzle-conditions.d.ts +63 -3
- package/dist/utils/error-cause.d.ts +7 -0
- package/dist/utils/pg-error-utils.d.ts +2 -4
- package/dist/utils/result-rows.d.ts +10 -0
- package/dist/websocket.d.ts +63 -18
- package/package.json +7 -7
- package/dist/BranchService-DGPL6G_C.js.map +0 -1
- package/dist/backup-cli-24v4OlSp.js.map +0 -1
- package/dist/backup-service-HQ9GC4tN.js.map +0 -1
- package/dist/cli-errors-B8qHg02P.js +0 -798
- package/dist/cli-errors-B8qHg02P.js.map +0 -1
- package/dist/cli-flags-BglvpjHv.js.map +0 -1
- package/dist/cli-output-CNdMql-L.js +0 -47
- package/dist/cli-output-CNdMql-L.js.map +0 -1
- package/dist/collection-index-DdnsxiJ_.js +0 -286
- package/dist/collection-index-DdnsxiJ_.js.map +0 -1
- package/dist/column-plan-helpers-DF-8dTVa.js.map +0 -1
- package/dist/databasePoolManager-Bj5FbeAs.js.map +0 -1
- package/dist/doctor-Cb2thZ8s.js.map +0 -1
- package/dist/ensure-collection-policies-RHUcEp4v.js +0 -105
- package/dist/ensure-collection-policies-RHUcEp4v.js.map +0 -1
- package/dist/ensure-collection-tables-BEEjn5cn.js +0 -857
- package/dist/ensure-collection-tables-BEEjn5cn.js.map +0 -1
- package/dist/ensure-tables-Dhn9KM3B.js.map +0 -1
- package/dist/generate-drizzle-schema-logic-BfzK7UQd.js.map +0 -1
- package/dist/generate-drizzle-schema-yzY_BLhr.js +0 -123
- package/dist/generate-drizzle-schema-yzY_BLhr.js.map +0 -1
- package/dist/generate-postgres-ddl-logic-Bt2d2mRH.js.map +0 -1
- package/dist/introspect-db-logic-WMuAfxvw.js.map +0 -1
- package/dist/plan-schema-C0fxM8dY.js.map +0 -1
- package/dist/policy-drift-DljYdrpW.js.map +0 -1
- package/dist/rls-bootstrap-sql-B8EclDyM.js.map +0 -1
- package/dist/rls-enforcement-C6Xk0lA6.js.map +0 -1
- package/dist/source-Br7L7GOI.js +0 -410
- package/dist/source-Br7L7GOI.js.map +0 -1
package/dist/auth/services.d.ts
CHANGED
|
@@ -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,
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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-
|
|
132
|
+
//# sourceMappingURL=auth-users-columns-C72EMoDJ.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-users-columns-
|
|
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"}
|
|
@@ -4,6 +4,20 @@
|
|
|
4
4
|
* backup surface stays self-contained.
|
|
5
5
|
*/
|
|
6
6
|
import 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>;
|
|
7
21
|
export declare function backupCommand(rawArgs: string[]): Promise<void>;
|
|
8
22
|
/** Parse a `rebase db restore …` line; the first positional is the backup. */
|
|
9
23
|
export declare function parseRestoreLine(rawArgs: string[]): arg.Result<{
|
|
@@ -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
|
-
|
|
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;
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { createRequire as __createRequire } from "module";
|
|
2
2
|
__createRequire(import.meta.url);
|
|
3
3
|
import { a as __toESM, t as require___vite_browser_external } from "./__vite-browser-external-BnuHet1e.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { t as chalk } from "./source-Br7L7GOI.js";
|
|
4
|
+
import { a as outWarn, i as outError, r as out, t as chalk } from "./source-DBDbBTL4.js";
|
|
5
|
+
import { A as parseDbNameFromUrl, E as globalsFileForDump, F as withDatabaseName, M as resolveConnectionString, O as parseBackupDestination, f as restoreDump, i as createDump, l as listBackups, m as validateDump, n as applyGlobals, o as discardPartialDump, p as uploadBackup, s as ensureDatabaseExists, t as BackupToolError, u as preflight } from "./backup-service-BTBB7RXn.js";
|
|
7
6
|
import { t as require_arg } from "./arg-Dni7MzLB.js";
|
|
8
|
-
import { r as parseDriverLine } from "./cli-flags-
|
|
9
|
-
import { parseEnvBoolean } from "@rebasepro/types";
|
|
10
|
-
import fs from "fs";
|
|
7
|
+
import { r as parseDriverLine } from "./cli-flags-B-Dawocl.js";
|
|
11
8
|
import path from "path";
|
|
9
|
+
import fs from "fs";
|
|
12
10
|
import os from "os";
|
|
11
|
+
import { Readable } from "stream";
|
|
12
|
+
import { pipeline } from "stream/promises";
|
|
13
13
|
//#region src/backup/backup-cli.ts
|
|
14
14
|
var import_arg = /* @__PURE__ */ __toESM(require_arg(), 1);
|
|
15
15
|
var import___vite_browser_external = /* @__PURE__ */ __toESM(require___vite_browser_external(), 1);
|
|
@@ -19,30 +19,38 @@ function formatBytes(bytes) {
|
|
|
19
19
|
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
20
20
|
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
|
21
21
|
}
|
|
22
|
+
/** A web stream's chunks, for piping into a file. */
|
|
23
|
+
async function* webChunks(stream) {
|
|
24
|
+
const reader = stream.getReader();
|
|
25
|
+
try {
|
|
26
|
+
for (;;) {
|
|
27
|
+
const { done, value } = await reader.read();
|
|
28
|
+
if (done) return;
|
|
29
|
+
yield value;
|
|
30
|
+
}
|
|
31
|
+
} finally {
|
|
32
|
+
reader.releaseLock();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
22
35
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
36
|
+
* The controller for an object-storage destination, or `null` for a local one.
|
|
37
|
+
*
|
|
38
|
+
* Built by `backupStorageFromEnv` from `@rebasepro/server` — the function the
|
|
39
|
+
* Backups panel's routes use — so the CLI and the panel read a bucket the same
|
|
40
|
+
* way. This file had its own copy, and the two had already drifted once
|
|
41
|
+
* (`S3_FORCE_PATH_STYLE` read as `=== "true"`, so a MinIO backup addressed the
|
|
42
|
+
* bucket as a host).
|
|
43
|
+
*
|
|
44
|
+
* Exported for its test.
|
|
25
45
|
*/
|
|
26
46
|
async function resolveStorageForDestination(dest, env) {
|
|
27
47
|
if (dest.kind === "local") return null;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
});
|
|
48
|
+
const { backupStorageFromEnv } = await import("@rebasepro/server");
|
|
49
|
+
try {
|
|
50
|
+
return backupStorageFromEnv(dest, env);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
throw new BackupToolError(error instanceof Error ? error.message : String(error), "Set the same S3_* variables your backend uses for storage.");
|
|
34
53
|
}
|
|
35
|
-
if (!env.S3_ACCESS_KEY_ID || !env.S3_SECRET_ACCESS_KEY) throw new BackupToolError("S3 destination requires S3_ACCESS_KEY_ID and S3_SECRET_ACCESS_KEY in the environment.", "Set the same S3_* variables your backend uses for storage.");
|
|
36
|
-
const { S3StorageController } = await import("@rebasepro/server");
|
|
37
|
-
return new S3StorageController({
|
|
38
|
-
type: "s3",
|
|
39
|
-
bucket: dest.bucket,
|
|
40
|
-
region: env.S3_REGION || "auto",
|
|
41
|
-
accessKeyId: env.S3_ACCESS_KEY_ID,
|
|
42
|
-
secretAccessKey: env.S3_SECRET_ACCESS_KEY,
|
|
43
|
-
endpoint: env.S3_ENDPOINT,
|
|
44
|
-
forcePathStyle: parseEnvBoolean(env.S3_FORCE_PATH_STYLE)
|
|
45
|
-
});
|
|
46
54
|
}
|
|
47
55
|
function requireConnection() {
|
|
48
56
|
const conn = resolveConnectionString(process.env);
|
|
@@ -196,12 +204,12 @@ async function restoreCommand(rawArgs) {
|
|
|
196
204
|
if (!storage) throw new BackupToolError("Could not resolve storage for the given URL.");
|
|
197
205
|
const key = backupArg.replace(/^(s3|gs):\/\/[^/]+\//, "");
|
|
198
206
|
const bucket = backupArg.replace(/^(s3|gs):\/\/([^/]+)\/.*$/, "$2");
|
|
199
|
-
const file = await storage.getObject(key, bucket);
|
|
200
|
-
if (!file) throw new BackupToolError(`Backup not found in storage: ${backupArg}`);
|
|
201
207
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "rebase-restore-"));
|
|
202
208
|
localFile = path.join(tmpDir, path.basename(key));
|
|
203
|
-
fs.writeFileSync(localFile, Buffer.from(await file.arrayBuffer()));
|
|
204
209
|
cleanupTemp = true;
|
|
210
|
+
const object = storage.getObjectStream ? await storage.getObjectStream(key, { bucket }) : await storage.getObject(key, bucket).then((file) => file && { stream: file.stream() });
|
|
211
|
+
if (!object) throw new BackupToolError(`Backup not found in storage: ${backupArg}`);
|
|
212
|
+
await pipeline(Readable.from(webChunks(object.stream)), fs.createWriteStream(localFile));
|
|
205
213
|
const globalsObj = await storage.getObject(globalsFileForDump(key), bucket);
|
|
206
214
|
if (globalsObj) globalsSql = Buffer.from(await globalsObj.arrayBuffer()).toString("utf-8");
|
|
207
215
|
} else {
|
|
@@ -409,4 +417,4 @@ ${chalk.green.bold("Usage")}
|
|
|
409
417
|
//#endregion
|
|
410
418
|
export { backupCommand, backupsCommand, restoreCommand };
|
|
411
419
|
|
|
412
|
-
//# sourceMappingURL=backup-cli-
|
|
420
|
+
//# sourceMappingURL=backup-cli-aUfRv16h.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backup-cli-aUfRv16h.js","names":[],"sources":["../src/backup/backup-cli.ts"],"sourcesContent":["/**\n * CLI handlers for `rebase db backup`, `rebase db restore`, and\n * `rebase db backups list`. Kept out of the main `cli.ts` dispatcher so the\n * backup surface stays self-contained.\n */\nimport arg from \"arg\";\nimport path from \"path\";\nimport fs from \"fs\";\nimport os from \"os\";\nimport readline from \"readline\";\nimport { Readable } from \"stream\";\nimport { pipeline } from \"stream/promises\";\nimport chalk from \"chalk\";\n// Aliased: `out` is already a local in two of the commands below (the `--out`\n// backup destination).\nimport { out as print, outWarn, outError } from \"../cli-output\";\nimport { parseDriverLine } from \"../cli-flags\";\nimport type { StorageController } from \"@rebasepro/server\";\nimport {\n BackupDestination,\n globalsFileForDump,\n parseBackupDestination,\n parseDbNameFromUrl,\n resolveConnectionString,\n withDatabaseName\n} from \"./pg-tools\";\nimport {\n applyGlobals,\n BackupToolError,\n createDump,\n discardPartialDump,\n ensureDatabaseExists,\n listBackups,\n preflight,\n restoreDump,\n uploadBackup,\n validateDump\n} from \"./backup-service\";\n\nfunction formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;\n}\n\n/** A web stream's chunks, for piping into a file. */\nasync function* webChunks(stream: ReadableStream<Uint8Array>): AsyncGenerator<Uint8Array> {\n const reader = stream.getReader();\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) return;\n yield value;\n }\n } finally {\n reader.releaseLock();\n }\n}\n\n/**\n * The controller for an object-storage destination, or `null` for a local one.\n *\n * Built by `backupStorageFromEnv` from `@rebasepro/server` — the function the\n * Backups panel's routes use — so the CLI and the panel read a bucket the same\n * way. This file had its own copy, and the two had already drifted once\n * (`S3_FORCE_PATH_STYLE` read as `=== \"true\"`, so a MinIO backup addressed the\n * bucket as a host).\n *\n * Exported for its test.\n */\nexport async function resolveStorageForDestination(\n dest: BackupDestination,\n env: Record<string, string | undefined>\n): Promise<StorageController | null> {\n if (dest.kind === \"local\") return null;\n const { backupStorageFromEnv } = await import(\"@rebasepro/server\");\n try {\n return backupStorageFromEnv(dest, env);\n } catch (error) {\n throw new BackupToolError(\n error instanceof Error ? error.message : String(error),\n \"Set the same S3_* variables your backend uses for storage.\"\n );\n }\n}\n\nfunction requireConnection(): string {\n const conn = resolveConnectionString(process.env);\n if (!conn) {\n outError(chalk.red(\"✗ DATABASE_URL is not set. Make sure your .env file is configured.\"));\n process.exit(1);\n }\n return conn;\n}\n\nasync function promptConfirm(question: string): Promise<boolean> {\n // Non-interactive shells (CI, pipes) can't answer — treat as \"no\".\n if (!process.stdin.isTTY) return false;\n const rl = readline.createInterface({ input: process.stdin, output: process.stdout });\n try {\n const answer: string = await new Promise((resolve) => rl.question(question, resolve));\n return /^y(es)?$/i.test(answer.trim());\n } finally {\n rl.close();\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// rebase db backup\n// ─────────────────────────────────────────────────────────────────────────\nexport async function backupCommand(rawArgs: string[]): Promise<void> {\n const args = arg(\n {\n \"--out\": String,\n // The same alias `build` and `cloud env pull` carry. `--output` is\n // the canonical spelling in this CLI and this command took only\n // `--out`, so `rebase db backup --output ./backups` wrote to\n // ./backups *and* to the default directory — the flag was ignored\n // and its value became a positional.\n \"--output\": \"--out\",\n \"--exclude-schema\": [String],\n \"--no-owner\": Boolean,\n \"--enable-row-security\": Boolean,\n \"--row-security-role\": String,\n \"-o\": \"--out\"\n },\n { argv: rawArgs.slice(2), permissive: true }\n );\n\n if (args._.includes(\"--help\") || rawArgs.includes(\"--help\")) {\n printBackupHelp();\n return;\n }\n\n const connectionString = requireConnection();\n const dbName = parseDbNameFromUrl(connectionString) ?? \"database\";\n const out = args[\"--out\"] || process.env.BACKUP_DESTINATION || path.join(process.cwd(), \"backups\");\n const dest = parseBackupDestination(out);\n\n print(\"\");\n print(chalk.bold(\" 💾 Rebase DB Backup\"));\n print(chalk.gray(` Database: ${dbName}`));\n print(chalk.gray(` Destination: ${out}`));\n print(\"\");\n\n // Version pre-flight (doctor-style).\n const pf = await preflight(\"pg_dump\", connectionString);\n if (!pf.compatible) {\n outError(chalk.red(` ✗ ${pf.reason}`));\n process.exit(1);\n }\n print(chalk.gray(` Using pg_dump ${pf.toolMajor} against server ${pf.serverMajor}.`));\n\n // Opt-in, and loud. With row security on, pg_dump stops refusing to read\n // rows it cannot see and starts leaving them out — an exit-0 backup that\n // is quietly short. Anyone choosing that should know they chose it.\n const rowSecurity = args[\"--enable-row-security\"]\n ? { uid: \"rebase-db-backup\", roles: [args[\"--row-security-role\"] || \"admin\"] }\n : undefined;\n\n if (rowSecurity) {\n outWarn(\"\");\n outWarn(chalk.yellow(\" ⚠ Dumping with row-level security ON.\"));\n outWarn(chalk.gray(` Reading as roles [${rowSecurity.roles.join(\", \")}], which satisfies the generated`));\n outWarn(chalk.gray(\" `admin_full_access` policy. This backup contains exactly the rows those\"));\n outWarn(chalk.gray(\" policies admit — a table whose policies have no admin rule comes out short,\"));\n outWarn(chalk.gray(\" and pg_dump will not say so. Prefer granting the dumping role BYPASSRLS.\"));\n outWarn(\"\");\n }\n\n try {\n if (dest.kind === \"local\") {\n // Honour an explicit `…/name.dump` path; otherwise treat it as a\n // directory and auto-name the file.\n const explicitFile = dest.path.endsWith(\".dump\");\n const dump = await createDump({\n connectionString,\n dbName,\n outDir: explicitFile ? path.dirname(dest.path) : dest.path,\n fileName: explicitFile ? path.basename(dest.path) : undefined,\n excludeSchemas: args[\"--exclude-schema\"],\n noOwner: args[\"--no-owner\"],\n inheritStdio: true,\n rowSecurity\n });\n await assertDumpValid(dump.localFile);\n print(\"\");\n print(chalk.green(` ✓ Backup written to ${dump.localFile} (${formatBytes(dump.sizeBytes)})`));\n if (dump.globalsFile) {\n print(chalk.gray(` ✓ Roles captured to ${dump.globalsFile} (needed so RLS survives a restore).`));\n }\n } else {\n const storage = await resolveStorageForDestination(dest, process.env);\n const dump = await createDump({\n connectionString,\n dbName,\n excludeSchemas: args[\"--exclude-schema\"],\n noOwner: args[\"--no-owner\"],\n inheritStdio: true,\n rowSecurity\n });\n try {\n await assertDumpValid(dump.localFile);\n const uploaded = await uploadBackup(storage!, dump.localFile, dest);\n print(\"\");\n print(chalk.green(` ✓ Backup uploaded to ${uploaded.storageUrl} (${formatBytes(dump.sizeBytes)})`));\n // Upload the roles sidecar next to the dump so a restore can\n // recreate roles the dump's GRANT/RLS statements depend on.\n if (dump.globalsFile && fs.existsSync(dump.globalsFile)) {\n const globalsUpload = await uploadBackup(storage!, dump.globalsFile, dest);\n print(chalk.gray(` ✓ Roles uploaded to ${globalsUpload.storageUrl} (needed so RLS survives a restore).`));\n }\n print(chalk.gray(\" Ensure this bucket is private — backups may contain secrets and PII.\"));\n } finally {\n if (fs.existsSync(dump.localFile)) fs.unlinkSync(dump.localFile);\n if (dump.globalsFile && fs.existsSync(dump.globalsFile)) fs.unlinkSync(dump.globalsFile);\n }\n }\n print(\"\");\n } catch (err) {\n reportError(err);\n process.exit(1);\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// rebase db restore <backup>\n// ─────────────────────────────────────────────────────────────────────────\nconst RESTORE_FLAGS = {\n \"--target-db\": String,\n \"--create-db\": Boolean,\n \"--clean\": Boolean,\n \"--no-owner\": Boolean,\n \"--continue-on-error\": Boolean,\n \"--yes\": Boolean,\n \"-y\": \"--yes\"\n} satisfies arg.Spec;\n\n/** Parse a `rebase db restore …` line; the first positional is the backup. */\nexport function parseRestoreLine(rawArgs: string[]) {\n return parseDriverLine(RESTORE_FLAGS, rawArgs);\n}\n\nexport async function restoreCommand(rawArgs: string[]): Promise<void> {\n const args = parseRestoreLine(rawArgs);\n\n const backupArg = args._[0];\n if (!backupArg || rawArgs.includes(\"--help\")) {\n printRestoreHelp();\n if (!backupArg && !rawArgs.includes(\"--help\")) process.exit(1);\n return;\n }\n\n const baseConnection = requireConnection();\n\n // Choose the target connection: an explicit --target-db (or --create-db's\n // implied fresh db) swaps the database name so the live one isn't clobbered.\n const targetDb = args[\"--target-db\"] ?? parseDbNameFromUrl(baseConnection) ?? undefined;\n const targetConnection = args[\"--target-db\"]\n ? withDatabaseName(baseConnection, args[\"--target-db\"])\n : baseConnection;\n\n print(\"\");\n print(chalk.bold(\" ♻️ Rebase DB Restore\"));\n print(chalk.gray(` Source: ${backupArg}`));\n print(chalk.gray(` Target: ${targetDb ?? \"(from DATABASE_URL)\"}`));\n print(\"\");\n\n // Resolve the local file to restore from (download object-storage keys).\n // Also resolve the `.globals.sql` roles sidecar so cluster roles can be\n // recreated before the restore — without them the dump's GRANT/RLS\n // statements fail and RLS is silently lost.\n let localFile: string;\n let globalsSql: string | null = null;\n let cleanupTemp = false;\n try {\n if (/^(s3|gs):\\/\\//.test(backupArg)) {\n const dest = parseBackupDestination(backupArg.replace(/\\/[^/]+$/, \"\"));\n const storage = await resolveStorageForDestination(dest, process.env);\n if (!storage) throw new BackupToolError(\"Could not resolve storage for the given URL.\");\n const key = backupArg.replace(/^(s3|gs):\\/\\/[^/]+\\//, \"\");\n const bucket = backupArg.replace(/^(s3|gs):\\/\\/([^/]+)\\/.*$/, \"$2\");\n const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), \"rebase-restore-\"));\n localFile = path.join(tmpDir, path.basename(key));\n cleanupTemp = true;\n // Streamed to disk: buffered, a dump was held twice in memory and\n // one over 2 GiB could not be written in one call at all.\n const object = storage.getObjectStream\n ? await storage.getObjectStream(key, { bucket })\n : await storage.getObject(key, bucket).then((file) => file && { stream: file.stream() });\n if (!object) throw new BackupToolError(`Backup not found in storage: ${backupArg}`);\n await pipeline(Readable.from(webChunks(object.stream)), fs.createWriteStream(localFile));\n const globalsObj = await storage.getObject(globalsFileForDump(key), bucket);\n if (globalsObj) globalsSql = Buffer.from(await globalsObj.arrayBuffer()).toString(\"utf-8\");\n } else {\n localFile = path.resolve(backupArg);\n if (!fs.existsSync(localFile)) {\n outError(chalk.red(` ✗ Backup file not found: ${localFile}`));\n process.exit(1);\n }\n const globalsPath = globalsFileForDump(localFile);\n if (fs.existsSync(globalsPath)) globalsSql = fs.readFileSync(globalsPath, \"utf-8\");\n }\n\n // Version pre-flight. Check against the base connection — the server\n // version is identical for every database, and the target may not\n // exist yet when --create-db is used.\n const pf = await preflight(\"pg_restore\", baseConnection);\n if (!pf.compatible) {\n outError(chalk.red(` ✗ ${pf.reason}`));\n process.exit(1);\n }\n\n // `--create-db` needs a name before anything else can be decided.\n if (args[\"--create-db\"] && !targetDb) {\n outError(chalk.red(\" ✗ --create-db requires a resolvable target database name (use --target-db).\"));\n process.exit(1);\n }\n\n // Destructive-action gate. Restores overwrite data; never run without\n // an explicit yes (interactive confirmation or --yes).\n //\n // Ahead of `--create-db`, not after it. Creating the database first\n // meant an aborted run had already changed the cluster, while printing\n // \"No changes were made\" — and it left an empty database behind that a\n // second, confirmed run then reported as \"already exists\". The gate is\n // now the first thing that can stop the command, so its own message is\n // true whichever way the answer goes.\n if (!args[\"--yes\"]) {\n outWarn(chalk.yellow(\n ` ⚠️ This will restore into \"${targetDb ?? \"the target database\"}\" and may overwrite existing data.`\n ));\n const confirmed = await promptConfirm(chalk.yellow(\" Type 'yes' to continue: \"));\n if (!confirmed) {\n print(chalk.gray(\" Aborted. No changes were made.\"));\n process.exit(1);\n }\n }\n\n // Create the target database when requested.\n if (args[\"--create-db\"]) {\n const created = await ensureDatabaseExists(baseConnection, targetDb!);\n print(chalk.gray(created ? ` ✓ Created database \"${targetDb}\".` : ` • Database \"${targetDb}\" already exists.`));\n }\n\n // Recreate cluster roles before restoring so GRANT/RLS statements in\n // the dump apply. Best-effort and idempotent (see applyGlobals).\n if (globalsSql) {\n print(chalk.gray(\" Recreating cluster roles from the backup's roles sidecar…\"));\n const { applied, skipped } = await applyGlobals(\n targetConnection,\n globalsSql,\n (m) => print(chalk.gray(m))\n );\n print(chalk.gray(` ✓ Roles: ${applied} applied, ${skipped} skipped (already present or not permitted).`));\n } else {\n outWarn(chalk.yellow(\" ⚠️ No roles sidecar (.globals.sql) accompanies this backup.\"));\n outWarn(chalk.yellow(\" If the dump grants to roles that don't exist (e.g. rebase_user), the restore\"));\n outWarn(chalk.yellow(\" will fail — recreate those roles first, or use a backup that includes its globals.\"));\n }\n\n await restoreDump({\n connectionString: targetConnection,\n inputFile: localFile,\n clean: args[\"--clean\"],\n noOwner: args[\"--no-owner\"],\n // Fail loudly by default so a skipped GRANT never leaves RLS off.\n exitOnError: !args[\"--continue-on-error\"],\n inheritStdio: true\n });\n\n print(\"\");\n print(chalk.green(` ✓ Restore completed into \"${targetDb ?? \"the target database\"}\".`));\n print(\"\");\n } catch (err) {\n reportError(err);\n process.exit(1);\n } finally {\n if (cleanupTemp && typeof localFile! === \"string\" && fs.existsSync(localFile!)) {\n fs.rmSync(path.dirname(localFile!), { recursive: true, force: true });\n }\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// rebase db backups list\n// ─────────────────────────────────────────────────────────────────────────\nexport async function backupsCommand(rawArgs: string[]): Promise<void> {\n const action = rawArgs[2];\n if (!action || action === \"--help\") {\n printBackupsHelp();\n return;\n }\n if (action !== \"list\") {\n outError(chalk.red(`Unknown backups action: \"${action}\". Valid: list`));\n process.exit(1);\n }\n\n const args = arg({ \"--out\": String, \"--output\": \"--out\", \"-o\": \"--out\" }, { argv: rawArgs.slice(3), permissive: true });\n const out = args[\"--out\"] || process.env.BACKUP_DESTINATION || path.join(process.cwd(), \"backups\");\n const dest = parseBackupDestination(out);\n\n try {\n const storage = await resolveStorageForDestination(dest, process.env);\n const backups = await listBackups(dest, storage ?? undefined);\n print(\"\");\n if (backups.length === 0) {\n print(chalk.gray(` No backups found at ${out}.`));\n } else {\n print(chalk.bold(` 💾 ${backups.length} backup(s) at ${out}:`));\n print(\"\");\n for (const b of backups) {\n const when = b.createdAt ? b.createdAt.toISOString() : \"unknown date\";\n const name = dest.kind === \"local\" ? path.basename(b.key) : b.key;\n // An empty file is called out rather than listed as a peer of\n // the real ones. Older failures could leave a 0-byte dump here\n // (fixed at the source now), and retention protects the newest\n // by date whatever they contain — so a corpse left in place can\n // hold a `keepMinimum` slot against a backup that matters.\n const empty = b.sizeBytes === 0;\n const size = b.sizeBytes === undefined ? \"\" : ` — ${formatBytes(b.sizeBytes)}`;\n if (empty) {\n print(` ${chalk.red(\"○\")} ${chalk.bold(name)} ${chalk.gray(`— ${when}`)}${chalk.red(\" — EMPTY, not restorable\")}`);\n } else {\n print(` ${chalk.green(\"●\")} ${chalk.bold(name)} ${chalk.gray(`— ${when}${size}`)}`);\n }\n }\n if (backups.some(b => b.sizeBytes === 0)) {\n print(\"\");\n print(chalk.yellow(\" ⚠ Empty files above are leftovers from a failed backup. Delete them:\"));\n print(chalk.gray(\" they count as recent backups for retention but restore nothing.\"));\n }\n }\n print(\"\");\n } catch (err) {\n reportError(err);\n process.exit(1);\n }\n}\n\n/**\n * Verify a freshly written dump; abort the command if it looks corrupt.\n *\n * Discards the artifact on the way out. Refusing to *report* success was never\n * enough on its own: the file stayed on disk, `rebase db backups list` showed\n * it as an ordinary entry, and retention — which ranks by timestamp and never\n * looks at size — would protect it as one of the `keepMinimum` newest while\n * pruning a real backup underneath it. The roles sidecar goes too; the two are\n * uploaded and pruned as a pair, so half a pair is not a backup either.\n */\nasync function assertDumpValid(localFile: string): Promise<void> {\n const check = await validateDump(localFile);\n if (!check.ok) {\n discardPartialDump(globalsFileForDump(localFile));\n discardPartialDump(localFile);\n throw new BackupToolError(\n `The backup failed validation and was discarded: ${check.reason}`,\n \"The dump was corrupt or truncated, so nothing was kept. Investigate before relying on this destination.\"\n );\n }\n}\n\nfunction reportError(err: unknown): void {\n if (err instanceof BackupToolError) {\n outError(chalk.red(` ✗ ${err.message}`));\n if (err.hint) outError(chalk.gray(` ${err.hint}`));\n } else {\n outError(chalk.red(` ✗ ${err instanceof Error ? err.message : String(err)}`));\n }\n}\n\nfunction printBackupHelp(): void {\n print(`\n${chalk.bold(\"rebase db backup\")} — Create a database backup (pg_dump, custom format)\n\n${chalk.green.bold(\"Usage\")}\n rebase db backup [--out <path|s3://bucket/prefix>] [options]\n\n${chalk.green.bold(\"Options\")}\n ${chalk.blue(\"--out, -o\")} <dest> Local path or s3://…/gs://… URL (default: ./backups)\n ${chalk.blue(\"--exclude-schema\")} <s> Exclude a schema (repeatable)\n ${chalk.blue(\"--no-owner\")} Omit ownership commands from the dump\n ${chalk.blue(\"--enable-row-security\")} Dump as an admin subject instead of failing on RLS\n ${chalk.red(\"(may produce a partial dump — see below)\")}\n ${chalk.blue(\"--row-security-role\")} <r> Role to read as with the flag above (default: admin)\n\n${chalk.green.bold(\"Row-level security\")}\n On a managed Postgres the dumping role usually owns nothing and has no\n BYPASSRLS, so pg_dump refuses:\n\n ERROR: query would be affected by row-level security policy for table \"...\"\n\n That refusal is the safe behaviour. --enable-row-security replaces it by\n reading as an admin subject: Rebase sets app.uid/app.user_roles so the\n generated admin_full_access policy admits the dump. The dump then contains\n exactly what those policies admit ${chalk.red(\"and no error is raised for what they do not\")} —\n a table whose policies lack an admin rule comes out short, silently.\n\n Granting the dumping role BYPASSRLS is the option that keeps a backup\n meaning \"every row\".\n\n${chalk.green.bold(\"Notes\")}\n Backups may contain secrets and PII. Use private storage destinations and\n enable encryption-at-rest. See docs/backups.md.\n`);\n}\n\nfunction printRestoreHelp(): void {\n print(`\n${chalk.bold(\"rebase db restore\")} — Restore a database from a backup (pg_restore)\n\n${chalk.green.bold(\"Usage\")}\n rebase db restore <backup> [options]\n\n${chalk.green.bold(\"Arguments\")}\n <backup> Local .dump file, or s3://…/gs://… object key\n\n${chalk.green.bold(\"Options\")}\n ${chalk.blue(\"--target-db\")} <name> Restore into this database instead of DATABASE_URL's\n ${chalk.blue(\"--create-db\")} Create the target database first if it doesn't exist\n ${chalk.blue(\"--clean\")} Drop existing objects before recreating them\n ${chalk.blue(\"--no-owner\")} Ignore ownership from the dump\n ${chalk.blue(\"--continue-on-error\")} Log and continue past errors ${chalk.red(\"(may leave RLS un-enforced!)\")}\n ${chalk.blue(\"--yes, -y\")} Skip the interactive confirmation ${chalk.red(\"(destructive!)\")}\n\n${chalk.red.bold(\"Warning\")}\n Restore is destructive and never runs automatically. Without --yes it\n requires an interactive 'yes'. Prefer --create-db/--target-db to restore\n into a fresh database rather than overwriting a live one.\n\n By default the restore aborts on the first error (--exit-on-error) so a\n skipped GRANT never silently leaves RLS un-enforced. Roles are recreated\n from the backup's .globals.sql sidecar first; keep that file next to the\n dump. Use --continue-on-error only when you understand the consequences.\n`);\n}\n\nfunction printBackupsHelp(): void {\n print(`\n${chalk.bold(\"rebase db backups\")} — Manage stored backups\n\n${chalk.green.bold(\"Usage\")}\n rebase db backups list [--out <path|s3://bucket/prefix>]\n rebase db backup list [--out <path|s3://bucket/prefix>] ${chalk.gray(\"(the same thing)\")}\n`);\n}\n"],"mappings":"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"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { createRequire as __createRequire } from "module";
|
|
2
2
|
__createRequire(import.meta.url);
|
|
3
3
|
import { r as __exportAll } from "./__vite-browser-external-BnuHet1e.js";
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
4
|
+
import { F as resolveLocalBin } from "./cli-errors-CaI4ntyD.js";
|
|
5
|
+
import { o as execa, s as forLibpq } from "./rls-bootstrap-sql-RQUljnJ1.js";
|
|
6
6
|
import path from "path";
|
|
7
|
+
import fs from "fs";
|
|
7
8
|
import os from "os";
|
|
8
9
|
//#region src/backup/pg-tools.ts
|
|
9
10
|
/**
|
|
@@ -320,6 +321,29 @@ function selectBackupsToPrune(backups, options) {
|
|
|
320
321
|
return toDelete.map((b) => b.key);
|
|
321
322
|
}
|
|
322
323
|
//#endregion
|
|
324
|
+
//#region src/backup/pg-binary.ts
|
|
325
|
+
/**
|
|
326
|
+
* Where the PostgreSQL client tools are, without spawning them.
|
|
327
|
+
*
|
|
328
|
+
* Kept apart from `backup-service.ts`, which pulls in `execa` (ESM-only), so
|
|
329
|
+
* `createBackupCron` can check for the tools when it is registered.
|
|
330
|
+
*/
|
|
331
|
+
var OVERRIDE_VARIABLE = {
|
|
332
|
+
pg_dump: "PG_DUMP_PATH",
|
|
333
|
+
pg_restore: "PG_RESTORE_PATH",
|
|
334
|
+
pg_dumpall: "PG_DUMPALL_PATH"
|
|
335
|
+
};
|
|
336
|
+
/** The variable that points at `tool` when it is not on the PATH. */
|
|
337
|
+
function pgToolVariable(tool) {
|
|
338
|
+
return OVERRIDE_VARIABLE[tool];
|
|
339
|
+
}
|
|
340
|
+
/** Locate `pg_dump` / `pg_restore` / `pg_dumpall`, honouring an env override. */
|
|
341
|
+
function resolvePgBinary(tool, env = process.env) {
|
|
342
|
+
const override = env[OVERRIDE_VARIABLE[tool]];
|
|
343
|
+
if (override && fs.existsSync(override)) return override;
|
|
344
|
+
return resolveLocalBin(tool);
|
|
345
|
+
}
|
|
346
|
+
//#endregion
|
|
323
347
|
//#region src/backup/backup-logic.ts
|
|
324
348
|
/**
|
|
325
349
|
* Pure-ish orchestration for the backup/restore paths, kept free of `execa`
|
|
@@ -438,12 +462,6 @@ var BackupToolError = class extends Error {
|
|
|
438
462
|
this.name = "BackupToolError";
|
|
439
463
|
}
|
|
440
464
|
};
|
|
441
|
-
/** Locate `pg_dump` / `pg_restore` / `pg_dumpall`, honouring an env override. */
|
|
442
|
-
function resolvePgBinary(tool, env = process.env) {
|
|
443
|
-
const overrideVar = tool === "pg_dump" ? env.PG_DUMP_PATH : tool === "pg_restore" ? env.PG_RESTORE_PATH : env.PG_DUMPALL_PATH;
|
|
444
|
-
if (overrideVar && fs.existsSync(overrideVar)) return overrideVar;
|
|
445
|
-
return resolveLocalBin(tool);
|
|
446
|
-
}
|
|
447
465
|
/** Run `<bin> --version` and extract the major version. */
|
|
448
466
|
async function detectToolMajor(bin) {
|
|
449
467
|
try {
|
|
@@ -655,8 +673,7 @@ async function ensureDatabaseExists(adminConnectionString, dbName) {
|
|
|
655
673
|
async function uploadBackup(storage, localFile, dest) {
|
|
656
674
|
const fileName = path.basename(localFile);
|
|
657
675
|
const key = joinStorageKey(dest.prefix, fileName);
|
|
658
|
-
const
|
|
659
|
-
const file = new File([buffer], fileName, { type: "application/octet-stream" });
|
|
676
|
+
const file = new File([await fs.openAsBlob(localFile)], fileName, { type: "application/octet-stream" });
|
|
660
677
|
return {
|
|
661
678
|
key,
|
|
662
679
|
storageUrl: (await storage.putObject({
|
|
@@ -664,35 +681,50 @@ async function uploadBackup(storage, localFile, dest) {
|
|
|
664
681
|
key,
|
|
665
682
|
bucket: dest.bucket,
|
|
666
683
|
metadata: { "rebase-backup": "1" }
|
|
667
|
-
})).storageUrl
|
|
684
|
+
}, { uploadLimits: false })).storageUrl
|
|
668
685
|
};
|
|
669
686
|
}
|
|
687
|
+
/** Stats in flight at once while listing a local backup folder. */
|
|
688
|
+
var LOCAL_STAT_CONCURRENCY = 32;
|
|
689
|
+
/** `fn` over `items`, at most `limit` at a time; results in input order. */
|
|
690
|
+
async function mapBounded(items, limit, fn) {
|
|
691
|
+
const results = new Array(items.length);
|
|
692
|
+
let next = 0;
|
|
693
|
+
const worker = async () => {
|
|
694
|
+
while (next < items.length) {
|
|
695
|
+
const i = next++;
|
|
696
|
+
results[i] = await fn(items[i]);
|
|
697
|
+
}
|
|
698
|
+
};
|
|
699
|
+
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
|
|
700
|
+
return results;
|
|
701
|
+
}
|
|
670
702
|
/**
|
|
671
703
|
* List existing backups at a destination. For local destinations reads the
|
|
672
704
|
* directory; for object storage lists the prefix via the controller.
|
|
673
705
|
*/
|
|
674
706
|
async function listBackups(dest, storage) {
|
|
675
707
|
if (dest.kind === "local") {
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
708
|
+
const stat = await fs.promises.stat(dest.path).catch(() => null);
|
|
709
|
+
if (!stat) return [];
|
|
710
|
+
const dir = stat.isDirectory() ? dest.path : path.dirname(dest.path);
|
|
711
|
+
const dumps = (await fs.promises.readdir(dir)).filter((f) => f.endsWith(".dump"));
|
|
712
|
+
const stats = await mapBounded(dumps, LOCAL_STAT_CONCURRENCY, (f) => fs.promises.stat(path.join(dir, f)));
|
|
713
|
+
return dumps.map((f, i) => {
|
|
714
|
+
const createdAt = parseBackupTimestamp(f) ?? stats[i].mtime;
|
|
681
715
|
return {
|
|
682
|
-
key:
|
|
683
|
-
createdAt
|
|
684
|
-
sizeBytes: stats.size
|
|
716
|
+
key: path.join(dir, f),
|
|
717
|
+
createdAt,
|
|
718
|
+
sizeBytes: stats[i].size
|
|
685
719
|
};
|
|
686
720
|
}).sort((a, b) => (b.createdAt?.getTime() ?? 0) - (a.createdAt?.getTime() ?? 0));
|
|
687
721
|
}
|
|
688
722
|
if (!storage) throw new BackupToolError(`Listing ${dest.kind} backups requires a configured storage backend.`);
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
createdAt: parseBackupTimestamp(key) ?? void 0
|
|
695
|
-
})).sort((a, b) => (b.createdAt?.getTime() ?? 0) - (a.createdAt?.getTime() ?? 0));
|
|
723
|
+
const { listBackupObjects } = await import("@rebasepro/server");
|
|
724
|
+
return (await listBackupObjects(dest, storage)).map((info) => ({
|
|
725
|
+
key: info.key,
|
|
726
|
+
createdAt: info.createdAt ? new Date(info.createdAt) : void 0
|
|
727
|
+
}));
|
|
696
728
|
}
|
|
697
729
|
/**
|
|
698
730
|
* Apply a retention policy to a destination, deleting the backups selected
|
|
@@ -710,6 +742,6 @@ async function pruneBackups(dest, options, storage) {
|
|
|
710
742
|
return toDelete;
|
|
711
743
|
}
|
|
712
744
|
//#endregion
|
|
713
|
-
export {
|
|
745
|
+
export { parseDbNameFromUrl as A, buildRowSecurityPgOptions as C, joinStorageKey as D, globalsFileForDump as E, withDatabaseName as F, resolveConnectionString as M, serverVersionNumToMajor as N, parseBackupDestination as O, splitGlobalsStatements as P, buildPgRestoreListArgs as S, diagnoseRowSecurityDumpFailure as T, selectBackupsToPrune as _, detectToolMajor as a, buildPgDumpallGlobalsArgs as b, getServerVersionMajor as c, pruneBackups as d, restoreDump as f, resolvePgBinary as g, pgToolVariable as h, createDump as i, parsePgToolMajor as j, parseBackupTimestamp as k, listBackups as l, validateDump as m, applyGlobals as n, discardPartialDump as o, uploadBackup as p, backup_service_exports as r, ensureDatabaseExists as s, BackupToolError as t, preflight as u, buildBackupFilename as v, checkToolServerCompatibility as w, buildPgRestoreArgs as x, buildPgDumpArgs as y };
|
|
714
746
|
|
|
715
|
-
//# sourceMappingURL=backup-service-
|
|
747
|
+
//# sourceMappingURL=backup-service-BTBB7RXn.js.map
|