@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.
Files changed (83) hide show
  1. package/dist/{BranchService-CucnFcSE.js → BranchService-DGPL6G_C.js} +2 -2
  2. package/dist/{BranchService-CucnFcSE.js.map → BranchService-DGPL6G_C.js.map} +1 -1
  3. package/dist/PostgresBackendDriver.d.ts +41 -1
  4. package/dist/auth/services.d.ts +27 -8
  5. package/dist/backup/backup-cli.d.ts +22 -0
  6. package/dist/{backup-cli-CkjsJEcu.js → backup-cli-24v4OlSp.js} +17 -14
  7. package/dist/backup-cli-24v4OlSp.js.map +1 -0
  8. package/dist/{backup-service-BNLwvxuy.js → backup-service-HQ9GC4tN.js} +2 -2
  9. package/dist/{backup-service-BNLwvxuy.js.map → backup-service-HQ9GC4tN.js.map} +1 -1
  10. package/dist/{cli-errors-Dka89exj.js → cli-errors-B8qHg02P.js} +6 -1
  11. package/dist/cli-errors-B8qHg02P.js.map +1 -0
  12. package/dist/cli-flags-BglvpjHv.js +138 -0
  13. package/dist/cli-flags-BglvpjHv.js.map +1 -0
  14. package/dist/cli-flags.d.ts +28 -0
  15. package/dist/cli-helpers.d.ts +6 -0
  16. package/dist/cli-scratch-database.d.ts +34 -0
  17. package/dist/cli.js +97 -261
  18. package/dist/cli.js.map +1 -1
  19. package/dist/{column-plan-helpers-CpILzHJS.js → column-plan-helpers-DF-8dTVa.js} +16 -20
  20. package/dist/column-plan-helpers-DF-8dTVa.js.map +1 -0
  21. package/dist/{doctor-Gzzd8wTq.js → doctor-Cb2thZ8s.js} +8 -7
  22. package/dist/doctor-Cb2thZ8s.js.map +1 -0
  23. package/dist/{ensure-collection-policies-B4IDFtQ-.js → ensure-collection-policies-RHUcEp4v.js} +5 -5
  24. package/dist/{ensure-collection-policies-B4IDFtQ-.js.map → ensure-collection-policies-RHUcEp4v.js.map} +1 -1
  25. package/dist/{ensure-collection-tables-Cr2ye5JC.js → ensure-collection-tables-BEEjn5cn.js} +53 -22
  26. package/dist/ensure-collection-tables-BEEjn5cn.js.map +1 -0
  27. package/dist/{ensure-tables-BnEvEJPr.js → ensure-tables-Dhn9KM3B.js} +2 -2
  28. package/dist/{ensure-tables-BnEvEJPr.js.map → ensure-tables-Dhn9KM3B.js.map} +1 -1
  29. package/dist/{generate-drizzle-schema-logic-DFa9qy9u.js → generate-drizzle-schema-logic-BfzK7UQd.js} +2 -2
  30. package/dist/{generate-drizzle-schema-logic-DFa9qy9u.js.map → generate-drizzle-schema-logic-BfzK7UQd.js.map} +1 -1
  31. package/dist/{generate-drizzle-schema-D2MDdJt-.js → generate-drizzle-schema-yzY_BLhr.js} +2 -2
  32. package/dist/{generate-drizzle-schema-D2MDdJt-.js.map → generate-drizzle-schema-yzY_BLhr.js.map} +1 -1
  33. package/dist/{generate-postgres-ddl-logic-CR2xcS7e.js → generate-postgres-ddl-logic-Bt2d2mRH.js} +2 -2
  34. package/dist/{generate-postgres-ddl-logic-CR2xcS7e.js.map → generate-postgres-ddl-logic-Bt2d2mRH.js.map} +1 -1
  35. package/dist/index.es.js +1882 -392
  36. package/dist/index.es.js.map +1 -1
  37. package/dist/{introspect-db-logic-C6LQdTxj.js → introspect-db-logic-WMuAfxvw.js} +323 -4
  38. package/dist/introspect-db-logic-WMuAfxvw.js.map +1 -0
  39. package/dist/{plan-schema-Hgl62S-w.js → plan-schema-C0fxM8dY.js} +170 -38
  40. package/dist/plan-schema-C0fxM8dY.js.map +1 -0
  41. package/dist/{policy-drift-B0GDRh7_.js → policy-drift-DljYdrpW.js} +2 -2
  42. package/dist/{policy-drift-B0GDRh7_.js.map → policy-drift-DljYdrpW.js.map} +1 -1
  43. package/dist/{rls-bootstrap-sql-BHA6jLtj.js → rls-bootstrap-sql-B8EclDyM.js} +320 -17
  44. package/dist/rls-bootstrap-sql-B8EclDyM.js.map +1 -0
  45. package/dist/schema/atlas-argv.d.ts +15 -0
  46. package/dist/schema/column-plan-helpers.d.ts +23 -4
  47. package/dist/schema/destructive-sql.d.ts +52 -1
  48. package/dist/schema/doctor-cli.js +3 -3
  49. package/dist/schema/generate-drizzle-schema.js +1 -1
  50. package/dist/schema/generate-postgres-ddl.js +1 -1
  51. package/dist/schema/introspect-db.js +1 -312
  52. package/dist/schema/introspect-db.js.map +1 -1
  53. package/dist/schema/plan/diff-plan.d.ts +3 -2
  54. package/dist/schema/plan/plan-schema.d.ts +10 -3
  55. package/dist/schema/plan/types.d.ts +6 -1
  56. package/dist/schema/search-column.d.ts +21 -1
  57. package/dist/services/FetchService.d.ts +147 -30
  58. package/dist/services/PersistService.d.ts +56 -5
  59. package/dist/services/RelationService.d.ts +42 -1
  60. package/dist/services/RelationWriteService.d.ts +22 -1
  61. package/dist/services/cdc/CdcListener.d.ts +6 -1
  62. package/dist/services/collection-helpers.d.ts +11 -0
  63. package/dist/services/dataService.d.ts +19 -21
  64. package/dist/services/field-op-sql.d.ts +71 -0
  65. package/dist/services/junction-writes.d.ts +19 -3
  66. package/dist/services/pg-notify-listener.d.ts +7 -0
  67. package/dist/services/read-field-access.d.ts +19 -0
  68. package/dist/services/read-scope.d.ts +100 -0
  69. package/dist/services/realtimeService.d.ts +77 -3
  70. package/dist/services/row-pipeline.d.ts +5 -0
  71. package/dist/services/soft-delete.d.ts +0 -2
  72. package/dist/services/write-transaction-scope.d.ts +39 -0
  73. package/dist/utils/drizzle-conditions.d.ts +63 -8
  74. package/dist/websocket.d.ts +10 -3
  75. package/package.json +9 -9
  76. package/dist/backup-cli-CkjsJEcu.js.map +0 -1
  77. package/dist/cli-errors-Dka89exj.js.map +0 -1
  78. package/dist/column-plan-helpers-CpILzHJS.js.map +0 -1
  79. package/dist/doctor-Gzzd8wTq.js.map +0 -1
  80. package/dist/ensure-collection-tables-Cr2ye5JC.js.map +0 -1
  81. package/dist/introspect-db-logic-C6LQdTxj.js.map +0 -1
  82. package/dist/plan-schema-Hgl62S-w.js.map +0 -1
  83. 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
- /** How long an app-emit key suppresses its own CDC echo. Covers NOTIFY round-trip latency. */
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 removed (but not forcefully closed — the
709
- * HTTP server close will handle that).
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** — the original `ILIKE '%term%'` OR-ed across top-level
520
- * string properties, with the term escaped (see {@link escapeLikePattern})
521
- * so it is matched as the literal text the user typed.
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. A collection that has
524
- * not opted in compiles to exactly the SQL it compiled to before this
525
- * branch existed, which is the only reason it is safe to have added it.
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, idColumn?: AnyPgColumn, excludeId?: string | number): SQL[];
642
+ static buildUniqueFieldCondition(fieldColumn: AnyPgColumn, value: unknown, excludeRow?: SQL): SQL[];
588
643
  /**
589
644
  * Build relation-based query with joins and conditions
590
645
  */
@@ -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
- export declare function createPostgresWebSocket(server: Server, realtimeService: RealtimeService, driver: PostgresBackendDriver, authConfig?: WsAuthConfig, authAdapter?: AuthAdapter): void;
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.21.2-canary.g1ea48be",
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.21.2-canary.g1ea48be",
54
- "@rebasepro/common": "0.21.2-canary.g1ea48be",
55
- "@rebasepro/server": "0.21.2-canary.g1ea48be",
56
- "@rebasepro/types": "0.21.2-canary.g1ea48be",
57
- "@rebasepro/utils": "0.21.2-canary.g1ea48be"
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.0",
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.10",
73
- "@rebasepro/client": "0.21.2-canary.g1ea48be"
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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"}