@rebasepro/server-postgres 0.21.2-canary.g1ea48be → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{BranchService-CucnFcSE.js → BranchService-DGPL6G_C.js} +2 -2
- package/dist/{BranchService-CucnFcSE.js.map → BranchService-DGPL6G_C.js.map} +1 -1
- package/dist/PostgresBackendDriver.d.ts +41 -1
- package/dist/auth/services.d.ts +27 -8
- package/dist/backup/backup-cli.d.ts +22 -0
- package/dist/{backup-cli-CkjsJEcu.js → backup-cli-24v4OlSp.js} +17 -14
- package/dist/backup-cli-24v4OlSp.js.map +1 -0
- package/dist/{backup-service-BNLwvxuy.js → backup-service-HQ9GC4tN.js} +2 -2
- package/dist/{backup-service-BNLwvxuy.js.map → backup-service-HQ9GC4tN.js.map} +1 -1
- package/dist/{cli-errors-Dka89exj.js → cli-errors-B8qHg02P.js} +6 -1
- package/dist/cli-errors-B8qHg02P.js.map +1 -0
- package/dist/cli-flags-BglvpjHv.js +138 -0
- package/dist/cli-flags-BglvpjHv.js.map +1 -0
- package/dist/cli-flags.d.ts +28 -0
- package/dist/cli-helpers.d.ts +6 -0
- package/dist/cli-scratch-database.d.ts +34 -0
- package/dist/cli.js +97 -261
- package/dist/cli.js.map +1 -1
- package/dist/{column-plan-helpers-CpILzHJS.js → column-plan-helpers-DF-8dTVa.js} +16 -20
- package/dist/column-plan-helpers-DF-8dTVa.js.map +1 -0
- package/dist/{doctor-Gzzd8wTq.js → doctor-Cb2thZ8s.js} +8 -7
- package/dist/doctor-Cb2thZ8s.js.map +1 -0
- package/dist/{ensure-collection-policies-B4IDFtQ-.js → ensure-collection-policies-RHUcEp4v.js} +5 -5
- package/dist/{ensure-collection-policies-B4IDFtQ-.js.map → ensure-collection-policies-RHUcEp4v.js.map} +1 -1
- package/dist/{ensure-collection-tables-Cr2ye5JC.js → ensure-collection-tables-BEEjn5cn.js} +53 -22
- package/dist/ensure-collection-tables-BEEjn5cn.js.map +1 -0
- package/dist/{ensure-tables-BnEvEJPr.js → ensure-tables-Dhn9KM3B.js} +2 -2
- package/dist/{ensure-tables-BnEvEJPr.js.map → ensure-tables-Dhn9KM3B.js.map} +1 -1
- package/dist/{generate-drizzle-schema-logic-DFa9qy9u.js → generate-drizzle-schema-logic-BfzK7UQd.js} +2 -2
- package/dist/{generate-drizzle-schema-logic-DFa9qy9u.js.map → generate-drizzle-schema-logic-BfzK7UQd.js.map} +1 -1
- package/dist/{generate-drizzle-schema-D2MDdJt-.js → generate-drizzle-schema-yzY_BLhr.js} +2 -2
- package/dist/{generate-drizzle-schema-D2MDdJt-.js.map → generate-drizzle-schema-yzY_BLhr.js.map} +1 -1
- package/dist/{generate-postgres-ddl-logic-CR2xcS7e.js → generate-postgres-ddl-logic-Bt2d2mRH.js} +2 -2
- package/dist/{generate-postgres-ddl-logic-CR2xcS7e.js.map → generate-postgres-ddl-logic-Bt2d2mRH.js.map} +1 -1
- package/dist/index.es.js +1882 -392
- package/dist/index.es.js.map +1 -1
- package/dist/{introspect-db-logic-C6LQdTxj.js → introspect-db-logic-WMuAfxvw.js} +323 -4
- package/dist/introspect-db-logic-WMuAfxvw.js.map +1 -0
- package/dist/{plan-schema-Hgl62S-w.js → plan-schema-C0fxM8dY.js} +170 -38
- package/dist/plan-schema-C0fxM8dY.js.map +1 -0
- package/dist/{policy-drift-B0GDRh7_.js → policy-drift-DljYdrpW.js} +2 -2
- package/dist/{policy-drift-B0GDRh7_.js.map → policy-drift-DljYdrpW.js.map} +1 -1
- package/dist/{rls-bootstrap-sql-BHA6jLtj.js → rls-bootstrap-sql-B8EclDyM.js} +320 -17
- package/dist/rls-bootstrap-sql-B8EclDyM.js.map +1 -0
- package/dist/schema/atlas-argv.d.ts +15 -0
- package/dist/schema/column-plan-helpers.d.ts +23 -4
- package/dist/schema/destructive-sql.d.ts +52 -1
- package/dist/schema/doctor-cli.js +3 -3
- package/dist/schema/generate-drizzle-schema.js +1 -1
- package/dist/schema/generate-postgres-ddl.js +1 -1
- package/dist/schema/introspect-db.js +1 -312
- package/dist/schema/introspect-db.js.map +1 -1
- package/dist/schema/plan/diff-plan.d.ts +3 -2
- package/dist/schema/plan/plan-schema.d.ts +10 -3
- package/dist/schema/plan/types.d.ts +6 -1
- package/dist/schema/search-column.d.ts +21 -1
- package/dist/services/FetchService.d.ts +147 -30
- package/dist/services/PersistService.d.ts +56 -5
- package/dist/services/RelationService.d.ts +42 -1
- package/dist/services/RelationWriteService.d.ts +22 -1
- package/dist/services/cdc/CdcListener.d.ts +6 -1
- package/dist/services/collection-helpers.d.ts +11 -0
- package/dist/services/dataService.d.ts +19 -21
- package/dist/services/field-op-sql.d.ts +71 -0
- package/dist/services/junction-writes.d.ts +19 -3
- package/dist/services/pg-notify-listener.d.ts +7 -0
- package/dist/services/read-field-access.d.ts +19 -0
- package/dist/services/read-scope.d.ts +100 -0
- package/dist/services/realtimeService.d.ts +77 -3
- package/dist/services/row-pipeline.d.ts +5 -0
- package/dist/services/soft-delete.d.ts +0 -2
- package/dist/services/write-transaction-scope.d.ts +39 -0
- package/dist/utils/drizzle-conditions.d.ts +63 -8
- package/dist/websocket.d.ts +10 -3
- package/package.json +9 -9
- package/dist/backup-cli-CkjsJEcu.js.map +0 -1
- package/dist/cli-errors-Dka89exj.js.map +0 -1
- package/dist/column-plan-helpers-CpILzHJS.js.map +0 -1
- package/dist/doctor-Gzzd8wTq.js.map +0 -1
- package/dist/ensure-collection-tables-Cr2ye5JC.js.map +0 -1
- package/dist/introspect-db-logic-C6LQdTxj.js.map +0 -1
- package/dist/plan-schema-Hgl62S-w.js.map +0 -1
- package/dist/rls-bootstrap-sql-BHA6jLtj.js.map +0 -1
|
@@ -267,7 +267,19 @@ export declare class RealtimeService extends EventEmitter implements RealtimePro
|
|
|
267
267
|
* and always flow through. Keyed → expiry timestamp (ms).
|
|
268
268
|
*/
|
|
269
269
|
private recentAppEmits;
|
|
270
|
-
/**
|
|
270
|
+
/**
|
|
271
|
+
* How long an app-emit key suppresses its own CDC echo: the refetch
|
|
272
|
+
* debounce, and no longer.
|
|
273
|
+
*
|
|
274
|
+
* The mark cannot tell an echo from another writer's change to the same
|
|
275
|
+
* row, and a save that touches no row of the table (a to-many relation
|
|
276
|
+
* only, an empty payload) is announced with no echo ever coming to consume
|
|
277
|
+
* it. Held for seconds, it swallowed the next change psql, a cron or
|
|
278
|
+
* another instance made to that row. Inside the debounce nothing is lost:
|
|
279
|
+
* the refetch the app emit scheduled has not started yet — its timer fires
|
|
280
|
+
* no earlier than this — and it reads after any commit whose NOTIFY has
|
|
281
|
+
* already arrived. An echo later than that costs one more refetch.
|
|
282
|
+
*/
|
|
271
283
|
private static readonly CDC_DEDUP_WINDOW_MS;
|
|
272
284
|
constructor(db: NodePgDatabase<any>, registry: PostgresCollectionRegistry);
|
|
273
285
|
/**
|
|
@@ -366,6 +378,25 @@ export declare class RealtimeService extends EventEmitter implements RealtimePro
|
|
|
366
378
|
* synchronously, so nothing else can have answered the request yet.
|
|
367
379
|
*/
|
|
368
380
|
private reportSocketFetchFailure;
|
|
381
|
+
/**
|
|
382
|
+
* Re-judge everything a socket holds open as the identity it has now.
|
|
383
|
+
*
|
|
384
|
+
* A socket re-authenticates on every session refresh, and when a role is
|
|
385
|
+
* taken away, a tenant claim removed or a different account signs in, the
|
|
386
|
+
* next token says so. Requests read as the new identity at once; the
|
|
387
|
+
* subscriptions kept refetching as the one they were opened with, so a
|
|
388
|
+
* demoted user went on receiving editor-only or another tenant's rows for
|
|
389
|
+
* as long as the view stayed mounted.
|
|
390
|
+
*
|
|
391
|
+
* Each of this client's subscriptions is replaced by one carrying the new
|
|
392
|
+
* identity — a replacement, so a refetch already running as the old one
|
|
393
|
+
* cannot deliver — and refetched, so the view narrows now rather than at
|
|
394
|
+
* the next write. A subscription whose filter, sort or projection names a
|
|
395
|
+
* field the new roles may not read is ended with the refusal a new
|
|
396
|
+
* subscription would get. Channel memberships are put to the authorizer
|
|
397
|
+
* again, when one is installed, and left when it refuses.
|
|
398
|
+
*/
|
|
399
|
+
rescopeClient(clientId: string, authContext: SubscriptionAuthContext): Promise<void>;
|
|
369
400
|
private handleUnsubscribe;
|
|
370
401
|
/**
|
|
371
402
|
* Enhanced notification method that handles nested relation updates.
|
|
@@ -497,6 +528,23 @@ export declare class RealtimeService extends EventEmitter implements RealtimePro
|
|
|
497
528
|
* Extract parent paths from a nested path like "posts/70/tags"
|
|
498
529
|
* Returns ["posts", "posts/70"] for the example above
|
|
499
530
|
*/
|
|
531
|
+
/**
|
|
532
|
+
* The other paths a subscriber can address the row written at `path` by.
|
|
533
|
+
*
|
|
534
|
+
* `authors/1/posts` and `posts` are two addresses for the same rows, and
|
|
535
|
+
* subscriptions were matched by the written path's exact string. So a post
|
|
536
|
+
* saved through `authors/1/posts` never reached a subscriber of `posts` or
|
|
537
|
+
* of `posts/43`, and one saved through `posts` never reached a subscriber
|
|
538
|
+
* of `authors/1/posts`, whose list it may just have joined or left.
|
|
539
|
+
*
|
|
540
|
+
* The aliases are the target collection's root path, and every nested path
|
|
541
|
+
* a live subscription holds that lands on the same collection. Each still
|
|
542
|
+
* refetches under its own scope and its own parent, so naming a path here
|
|
543
|
+
* decides only who is asked to look again — never what they are shown.
|
|
544
|
+
*/
|
|
545
|
+
private aliasPaths;
|
|
546
|
+
/** The slug of the collection a path lands on, or `undefined` for one that names none. */
|
|
547
|
+
private collectionSlugAt;
|
|
500
548
|
private getParentPaths;
|
|
501
549
|
/**
|
|
502
550
|
* Install a channel authorizer — see {@link ChannelAuthorizer}.
|
|
@@ -705,8 +753,8 @@ export declare class RealtimeService extends EventEmitter implements RealtimePro
|
|
|
705
753
|
* 1. All debounced refetch timers are cancelled (prevents queries after pool closes).
|
|
706
754
|
* 2. All subscription state and callbacks are cleared.
|
|
707
755
|
* 3. The dedicated LISTEN client (outside the pool) is disconnected.
|
|
708
|
-
* 4. All WebSocket clients are
|
|
709
|
-
*
|
|
756
|
+
* 4. All WebSocket clients are closed with 1001 ("going away"); one that
|
|
757
|
+
* does not answer the close frame is dropped after a short grace.
|
|
710
758
|
*/
|
|
711
759
|
destroy(): Promise<void>;
|
|
712
760
|
/** Whether database-level change capture is currently the active source. */
|
|
@@ -729,6 +777,18 @@ export declare class RealtimeService extends EventEmitter implements RealtimePro
|
|
|
729
777
|
* (bypass PgBouncer — LISTEN needs a session connection).
|
|
730
778
|
*/
|
|
731
779
|
enableCdc(connectionString: string): Promise<void>;
|
|
780
|
+
/**
|
|
781
|
+
* Refetch every live subscription, as a change to its path would.
|
|
782
|
+
*
|
|
783
|
+
* Called when a LISTEN connection (CDC, or the cross-instance broadcast)
|
|
784
|
+
* is listening again after a drop. Postgres does not queue NOTIFY for a
|
|
785
|
+
* session that is not listening, so every change committed while it was
|
|
786
|
+
* down — by another instance, psql, a cron — reached nobody here, and a
|
|
787
|
+
* subscriber held its pre-gap rows until some later change to the same
|
|
788
|
+
* collection happened to arrive. Each refetch runs under the
|
|
789
|
+
* subscription's own scope, so this decides only who looks again.
|
|
790
|
+
*/
|
|
791
|
+
private resyncSubscriptions;
|
|
732
792
|
/** Stop the CDC listener and clear its state. */
|
|
733
793
|
stopCdc(): Promise<void>;
|
|
734
794
|
/**
|
|
@@ -769,6 +829,16 @@ export declare class RealtimeService extends EventEmitter implements RealtimePro
|
|
|
769
829
|
private handleJunctionCdcEvent;
|
|
770
830
|
/** Compute the canonical (possibly composite) id string from a captured row. */
|
|
771
831
|
private extractIdFromCdcRow;
|
|
832
|
+
/**
|
|
833
|
+
* A captured row with its key under the key's *field* names.
|
|
834
|
+
*
|
|
835
|
+
* The trigger captures the tuple by column — `user_id` — and an address is
|
|
836
|
+
* built from the key's property names — `userId`. Read by property name
|
|
837
|
+
* alone, a key declared apart from its column was never on the captured
|
|
838
|
+
* row, the address fell back to `*`, and no single-row subscriber heard
|
|
839
|
+
* about any write made outside this server.
|
|
840
|
+
*/
|
|
841
|
+
private cdcRowByKeyFields;
|
|
772
842
|
private dedupKey;
|
|
773
843
|
/** Record that this instance just delivered `key` via the app path. */
|
|
774
844
|
private markAppEmit;
|
|
@@ -796,6 +866,10 @@ export declare class RealtimeService extends EventEmitter implements RealtimePro
|
|
|
796
866
|
private broadcastChange;
|
|
797
867
|
/**
|
|
798
868
|
* Create and connect the dedicated LISTEN client with auto-reconnect.
|
|
869
|
+
*
|
|
870
|
+
* @param reconnect Set when the client is coming back from a drop: every
|
|
871
|
+
* notification published in the gap is gone, so the subscriptions
|
|
872
|
+
* are refetched once it is listening again.
|
|
799
873
|
*/
|
|
800
874
|
private connectListenClient;
|
|
801
875
|
/**
|
|
@@ -59,6 +59,11 @@ export declare function toRestValues(row: Record<string, unknown>, collection: C
|
|
|
59
59
|
* an `update` that echoes the row back overwrite the real value with the null it
|
|
60
60
|
* was handed.
|
|
61
61
|
*
|
|
62
|
+
* A withheld to-one relation takes its foreign key with it. `bandId: 7` names
|
|
63
|
+
* the salary band exactly as `band: { id: 7 }` does, and the row carries the
|
|
64
|
+
* column beside the relation — so withholding only the relation withheld
|
|
65
|
+
* nothing.
|
|
66
|
+
*
|
|
62
67
|
* `_matches` is filtered rather than deleted: it is the list of fields a text
|
|
63
68
|
* search hit, and a field the caller cannot read must not appear in it even
|
|
64
69
|
* though the array itself is theirs to see.
|
|
@@ -23,8 +23,6 @@ import type { CollectionConfig } from "@rebasepro/types";
|
|
|
23
23
|
* (`assertCollectionConfigs`), where the answer is a config change, rather than
|
|
24
24
|
* at the first delete, where it would be a 500 for whoever pressed the button.
|
|
25
25
|
*/
|
|
26
|
-
/** The default field, when `softDelete: true` names none. */
|
|
27
|
-
export declare const DEFAULT_SOFT_DELETE_FIELD = "deletedAt";
|
|
28
26
|
/** How a caller asks about deleted rows. See `FetchCollectionProps.withDeleted`. */
|
|
29
27
|
export type WithDeleted = boolean | "only" | undefined;
|
|
30
28
|
export interface SoftDeleteField {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type AmbientTransaction } from "@rebasepro/server";
|
|
2
|
+
import type { RebaseCallContext } from "@rebasepro/types";
|
|
2
3
|
import type { DrizzleClient } from "../interfaces.js";
|
|
3
4
|
type RunSql = (sqlText: string, params?: unknown[]) => Promise<Record<string, unknown>[]>;
|
|
4
5
|
/**
|
|
@@ -24,6 +25,10 @@ export declare class WriteTransactionScope implements AmbientTransaction {
|
|
|
24
25
|
private open;
|
|
25
26
|
private readonly inflight;
|
|
26
27
|
private readonly commitHooks;
|
|
28
|
+
/** The first tracked statement that failed, whether or not its caller caught it. */
|
|
29
|
+
private failure?;
|
|
30
|
+
private readonly settledHooks;
|
|
31
|
+
private settledContext?;
|
|
27
32
|
/** The transaction handle, for statements in this package that are already written against drizzle. */
|
|
28
33
|
tx?: DrizzleClient;
|
|
29
34
|
/** Attach the transaction, once it has begun. */
|
|
@@ -49,6 +54,40 @@ export declare class WriteTransactionScope implements AmbientTransaction {
|
|
|
49
54
|
* connection, as if outside any write.
|
|
50
55
|
*/
|
|
51
56
|
settle(): Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* Refuse to commit a transaction a failed statement has aborted.
|
|
59
|
+
*
|
|
60
|
+
* Once any statement fails, Postgres aborts the whole transaction, and
|
|
61
|
+
* catching the error in JavaScript does not undo that. The COMMIT that
|
|
62
|
+
* follows is answered with the tag `ROLLBACK` and no error, so the driver
|
|
63
|
+
* resolves it: the write reported success — 200, webhooks, realtime,
|
|
64
|
+
* the idempotency record — while nothing was stored. A callback that
|
|
65
|
+
* wrapped a lookup or a job enqueue in `try/catch` was enough.
|
|
66
|
+
*
|
|
67
|
+
* So one statement is asked of the transaction before its commit. On an
|
|
68
|
+
* aborted one it fails with `25P02`, and the write is refused and rolled
|
|
69
|
+
* back instead. A failed `context.data` write is not caught by this: it
|
|
70
|
+
* runs in a savepoint, is undone on its own, and leaves the transaction
|
|
71
|
+
* usable — which is what makes catching one safe.
|
|
72
|
+
*/
|
|
73
|
+
assertCommittable(): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* What a hook deferred with {@link afterSettled} is handed as `context`:
|
|
76
|
+
* the same caller, on a driver where every call is a transaction of its
|
|
77
|
+
* own. The write's transaction is gone by the time the hook runs, so a
|
|
78
|
+
* context bound to it would be bound to a connection back in the pool.
|
|
79
|
+
*/
|
|
80
|
+
setSettledContext(context: RebaseCallContext): void;
|
|
81
|
+
/**
|
|
82
|
+
* Run `fn` once this write's transaction is over, whichever way it went,
|
|
83
|
+
* outside it — for what a failure hook hands off (a job, a queue message,
|
|
84
|
+
* a webhook), which must not ride the transaction the failure rolls back.
|
|
85
|
+
* `false` when this scope has no context to hand it; the caller runs it
|
|
86
|
+
* itself then.
|
|
87
|
+
*/
|
|
88
|
+
afterSettled(fn: (context: RebaseCallContext) => Promise<void>): boolean;
|
|
89
|
+
/** The transaction committed or rolled back: run what was waiting for either. */
|
|
90
|
+
settled(): Promise<void>;
|
|
52
91
|
/**
|
|
53
92
|
* The transaction is over, whichever way it went. Nothing may reach it now:
|
|
54
93
|
* its client is back in the pool, and a callback's detached promise still
|
|
@@ -516,13 +516,20 @@ export declare class DrizzleConditionBuilder {
|
|
|
516
516
|
* - **Declared** — one `@@ websearch_to_tsquery` against the generated
|
|
517
517
|
* `tsvector` column. Stems, drops stopwords, AND-es the terms, reaches
|
|
518
518
|
* inside JSONB and arrays, and uses the GIN index.
|
|
519
|
-
* - **Not declared** —
|
|
520
|
-
* string properties, with
|
|
521
|
-
* so it is matched as the literal text
|
|
519
|
+
* - **Not declared** — `ILIKE '%term%'` per term, OR-ed across top-level
|
|
520
|
+
* string properties and AND-ed across the terms, with each term escaped
|
|
521
|
+
* (see {@link escapeLikePattern}) so it is matched as the literal text
|
|
522
|
+
* the user typed.
|
|
522
523
|
*
|
|
523
|
-
* The second is the default and stays the default
|
|
524
|
-
*
|
|
525
|
-
*
|
|
524
|
+
* The second is the default and stays the default, and it AND-es terms for
|
|
525
|
+
* the same reason `websearch_to_tsquery` does: a person typing two words
|
|
526
|
+
* into a search box means both, and a name lives in two columns. Matching
|
|
527
|
+
* the whole string per column instead — which is what this did — cannot
|
|
528
|
+
* find `sebastian melendez` on a row with `first_name`/`last_name`, and
|
|
529
|
+
* could not find `sebastian ` at all.
|
|
530
|
+
*
|
|
531
|
+
* Returns at most one condition. Every caller ORs what it gets back, which
|
|
532
|
+
* is right across columns and wrong across terms, so the AND is built here.
|
|
526
533
|
*
|
|
527
534
|
* `collection` is optional so that the callers which genuinely have no
|
|
528
535
|
* collection in hand — nested paths, derived views — keep working; without
|
|
@@ -544,6 +551,49 @@ export declare class DrizzleConditionBuilder {
|
|
|
544
551
|
* on user input, which `to_tsquery` does on so much as a stray parenthesis.
|
|
545
552
|
*/
|
|
546
553
|
static buildFullTextCondition(searchString: string, table: PgTable<any>, collection: CollectionConfig): SQL | undefined;
|
|
554
|
+
/**
|
|
555
|
+
* The substring half of {@link SearchMode} `"hybrid"`: every term found
|
|
556
|
+
* somewhere in the declared fields, with accents folded on both sides.
|
|
557
|
+
*
|
|
558
|
+
* Terms are AND-ed and fields OR-ed, exactly as the ILIKE default does and
|
|
559
|
+
* for the same reason — a person typing two words means both, and a name
|
|
560
|
+
* lives in two columns (`splitSearchTerms`). What it does *not* share with
|
|
561
|
+
* the default is its reach: these are the declared `search.fields`, so it
|
|
562
|
+
* folds accents, joins `text[]` and walks into JSONB through the same
|
|
563
|
+
* IMMUTABLE helpers the generated column uses.
|
|
564
|
+
*
|
|
565
|
+
* The needle is folded with the same function as the document, so
|
|
566
|
+
* `munoz` and `Muñoz` meet in the middle rather than one side being
|
|
567
|
+
* normalized and the other not — the subtle way to build a search that
|
|
568
|
+
* matches nothing.
|
|
569
|
+
*
|
|
570
|
+
* Interpolated as raw SQL because the field expressions are generated (they
|
|
571
|
+
* name columns and helper functions, which are identifiers, not values);
|
|
572
|
+
* the *term* is a bind parameter, escaped for LIKE first — see
|
|
573
|
+
* {@link escapeLikePattern} for what that escape is for.
|
|
574
|
+
*/
|
|
575
|
+
static buildFoldedSubstringCondition(searchString: string, spec: SearchColumnSpec): SQL | undefined;
|
|
576
|
+
/**
|
|
577
|
+
* Whether one text expression holds **any** term of the search string.
|
|
578
|
+
*
|
|
579
|
+
* The per-field question, where {@link buildFoldedSubstringCondition} asks
|
|
580
|
+
* the per-row one: `OR` across terms rather than `AND`, because a two-word
|
|
581
|
+
* query satisfied across two fields has each field carrying one term. Used
|
|
582
|
+
* by `_matches` to name the fields that caused a substring hit.
|
|
583
|
+
*/
|
|
584
|
+
static foldedSubstringOf(searchString: string, text: SQL): SQL;
|
|
585
|
+
/**
|
|
586
|
+
* One accent-folded `%term%` bind parameter per search term.
|
|
587
|
+
*
|
|
588
|
+
* The needle is folded with the same function as the document, so `munoz`
|
|
589
|
+
* and `Muñoz` meet in the middle instead of one side being normalized and
|
|
590
|
+
* the other not — the subtle way to build a search that matches nothing.
|
|
591
|
+
*
|
|
592
|
+
* A string with no terms at all is only whitespace, and stays the single
|
|
593
|
+
* literal pattern it is on the ILIKE path rather than becoming "no rows",
|
|
594
|
+
* so both paths answer a stray space the same way.
|
|
595
|
+
*/
|
|
596
|
+
private static foldedNeedles;
|
|
547
597
|
/**
|
|
548
598
|
* `websearch_to_tsquery(<config>, <normalized search string>)`.
|
|
549
599
|
*
|
|
@@ -582,9 +632,14 @@ export declare class DrizzleConditionBuilder {
|
|
|
582
632
|
*/
|
|
583
633
|
static buildSearchRankExpression(searchString: string, table: PgTable<any>, collection: CollectionConfig): SQL | undefined;
|
|
584
634
|
/**
|
|
585
|
-
* Build a unique field check condition
|
|
635
|
+
* Build a unique field check condition: rows holding `value`, other than
|
|
636
|
+
* the one `excludeRow` names.
|
|
637
|
+
*
|
|
638
|
+
* `excludeRow` is the whole identity of the row being saved — every key
|
|
639
|
+
* column — so excluding one member of a composite-key table does not
|
|
640
|
+
* exclude every row sharing its first key part.
|
|
586
641
|
*/
|
|
587
|
-
static buildUniqueFieldCondition(fieldColumn: AnyPgColumn, value: unknown,
|
|
642
|
+
static buildUniqueFieldCondition(fieldColumn: AnyPgColumn, value: unknown, excludeRow?: SQL): SQL[];
|
|
588
643
|
/**
|
|
589
644
|
* Build relation-based query with joins and conditions
|
|
590
645
|
*/
|
package/dist/websocket.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { RealtimeService } from "./services/realtimeService.js";
|
|
2
2
|
import { PostgresBackendDriver } from "./PostgresBackendDriver.js";
|
|
3
|
-
import type { AuthAdapter } from "@rebasepro/types";
|
|
4
|
-
import { Server } from "http";
|
|
3
|
+
import type { AuthAdapter, RealtimeSocketLimits } from "@rebasepro/types";
|
|
4
|
+
import type { Server } from "http";
|
|
5
5
|
/** Minimal subset of RebaseAuthConfig used by the WebSocket layer. */
|
|
6
6
|
interface WsAuthConfig {
|
|
7
7
|
requireAuth?: boolean;
|
|
@@ -41,5 +41,12 @@ export declare const ADMIN_ONLY_TYPES: Set<string>;
|
|
|
41
41
|
* adding one without answering it is a test failure.
|
|
42
42
|
*/
|
|
43
43
|
export declare const PUBLIC_TYPES: Set<string>;
|
|
44
|
-
|
|
44
|
+
/**
|
|
45
|
+
* `limits` are the data API's, for the same rows reached through this door:
|
|
46
|
+
* frames larger than its body limit close the socket with 1009 before they are
|
|
47
|
+
* read, and every data frame counts in the caller's data-API bucket — the one
|
|
48
|
+
* their HTTP requests count in — rather than only in a budget per connection,
|
|
49
|
+
* which a caller multiplied by opening more.
|
|
50
|
+
*/
|
|
51
|
+
export declare function createPostgresWebSocket(server: Server, realtimeService: RealtimeService, driver: PostgresBackendDriver, authConfig?: WsAuthConfig, authAdapter?: AuthAdapter, limits?: RealtimeSocketLimits): void;
|
|
45
52
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rebasepro/server-postgres",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"description": "PostgreSQL data source backend implementation for Rebase with Drizzle ORM",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cms",
|
|
@@ -50,11 +50,11 @@
|
|
|
50
50
|
"execa": "^9.6.1",
|
|
51
51
|
"pg": "^8.22.0",
|
|
52
52
|
"ws": "^8.21.1",
|
|
53
|
-
"@rebasepro/codegen": "0.
|
|
54
|
-
"@rebasepro/
|
|
55
|
-
"@rebasepro/server": "0.
|
|
56
|
-
"@rebasepro/
|
|
57
|
-
"@rebasepro/utils": "0.
|
|
53
|
+
"@rebasepro/codegen": "0.23.0",
|
|
54
|
+
"@rebasepro/types": "0.23.0",
|
|
55
|
+
"@rebasepro/server": "0.23.0",
|
|
56
|
+
"@rebasepro/common": "0.23.0",
|
|
57
|
+
"@rebasepro/utils": "0.23.0"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@electric-sql/pglite": "0.5.6",
|
|
@@ -64,13 +64,13 @@
|
|
|
64
64
|
"@types/node": "^26.1.2",
|
|
65
65
|
"@types/pg": "^8.20.0",
|
|
66
66
|
"@types/ws": "^8.18.1",
|
|
67
|
-
"hono": "^4.13.
|
|
67
|
+
"hono": "^4.13.5",
|
|
68
68
|
"jest": "^30.4.2",
|
|
69
69
|
"ts-jest": "^29.4.12",
|
|
70
70
|
"typescript": "^6.0.3",
|
|
71
71
|
"vite": "^8.1.5",
|
|
72
|
-
"vitest": "^4.1.
|
|
73
|
-
"@rebasepro/client": "0.
|
|
72
|
+
"vitest": "^4.1.11",
|
|
73
|
+
"@rebasepro/client": "0.23.0"
|
|
74
74
|
},
|
|
75
75
|
"gitHead": "d935eefa5aa8d1009a2398cfac2c1e4ee9aeb6b6",
|
|
76
76
|
"publishConfig": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"backup-cli-CkjsJEcu.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 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 type { StorageController } from \"@rebasepro/server\";\nimport { parseEnvBoolean } from \"@rebasepro/types\";\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/**\n * Build a StorageController for an object-storage destination from the same\n * `S3_*` env vars the backend uses. Returns `null` for local destinations.\n */\nasync function resolveStorageForDestination(\n dest: BackupDestination,\n env: Record<string, string | undefined>\n): Promise<StorageController | null> {\n if (dest.kind === \"local\") return null;\n if (dest.kind === \"gcs\") {\n const { GCSStorageController } = await import(\"@rebasepro/server\");\n return new GCSStorageController({ type: \"gcs\", bucket: dest.bucket });\n }\n // s3 (also covers R2/MinIO/Hetzner/GCS-interop via S3_ENDPOINT)\n if (!env.S3_ACCESS_KEY_ID || !env.S3_SECRET_ACCESS_KEY) {\n throw new BackupToolError(\n \"S3 destination requires S3_ACCESS_KEY_ID and S3_SECRET_ACCESS_KEY in the environment.\",\n \"Set the same S3_* variables your backend uses for storage.\"\n );\n }\n const { S3StorageController } = await import(\"@rebasepro/server\");\n return new S3StorageController({\n type: \"s3\",\n bucket: dest.bucket,\n region: env.S3_REGION || \"auto\",\n accessKeyId: env.S3_ACCESS_KEY_ID,\n secretAccessKey: env.S3_SECRET_ACCESS_KEY,\n endpoint: env.S3_ENDPOINT,\n // Unset stays unset, so the controller decides from the endpoint the\n // way it does for the runtime's own storage. `=== \"true\"` made it an\n // explicit `false`, and a MinIO backup addressed the bucket as a host.\n forcePathStyle: parseEnvBoolean(env.S3_FORCE_PATH_STYLE)\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// ─────────────────────────────────────────────────────────────────────────\nexport async function restoreCommand(rawArgs: string[]): Promise<void> {\n const args = arg(\n {\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 },\n { argv: rawArgs.slice(2), permissive: true }\n );\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 file = await storage.getObject(key, bucket);\n if (!file) throw new BackupToolError(`Backup not found in storage: ${backupArg}`);\n const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), \"rebase-restore-\"));\n localFile = path.join(tmpDir, path.basename(key));\n fs.writeFileSync(localFile, Buffer.from(await file.arrayBuffer()));\n cleanupTemp = true;\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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"}
|