@rebasepro/server-postgres 0.17.3 → 0.18.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 (149) hide show
  1. package/LICENSE +0 -1
  2. package/README.md +31 -0
  3. package/dist/backup-argv.d.ts +19 -0
  4. package/dist/branch-argv.d.ts +28 -0
  5. package/dist/branch-prune.d.ts +77 -0
  6. package/dist/cli-collections-path.d.ts +31 -0
  7. package/dist/cli-errors.d.ts +123 -0
  8. package/dist/cli-flags.d.ts +101 -0
  9. package/dist/cli-helpers.d.ts +94 -10
  10. package/dist/collection-index-C7LopMyX.js +247 -0
  11. package/dist/collection-index-C7LopMyX.js.map +1 -0
  12. package/dist/databasePoolManager.d.ts +27 -0
  13. package/dist/{ensure-collection-policies-UCqgv_8c.js → ensure-collection-policies-CagOzEPS.js} +6 -6
  14. package/dist/{ensure-collection-policies-UCqgv_8c.js.map → ensure-collection-policies-CagOzEPS.js.map} +1 -1
  15. package/dist/{ensure-collection-tables-DgVixhX3.js → ensure-collection-tables-DbAhrjnG.js} +614 -52
  16. package/dist/ensure-collection-tables-DbAhrjnG.js.map +1 -0
  17. package/dist/index.d.ts +12 -0
  18. package/dist/index.es.js +19307 -3059
  19. package/dist/index.es.js.map +1 -1
  20. package/dist/{rls-enforcement-DvAbL9YJ.js → rls-enforcement-ClJoTboT.js} +53 -189
  21. package/dist/rls-enforcement-ClJoTboT.js.map +1 -0
  22. package/dist/schema/column-type-drift.d.ts +79 -0
  23. package/dist/schema/doctor.d.ts +42 -0
  24. package/dist/schema/ensure-collection-tables.d.ts +90 -0
  25. package/dist/schema/generate-next-step.d.ts +30 -0
  26. package/dist/schema/generate-postgres-ddl-logic.d.ts +28 -0
  27. package/dist/schema/generated-schema-staleness.d.ts +37 -0
  28. package/dist/schema/introspect-db-queries.d.ts +34 -0
  29. package/dist/security/rls-enforcement.d.ts +63 -2
  30. package/dist/services/BranchService.d.ts +86 -1
  31. package/dist/services/collection-helpers.d.ts +1 -1
  32. package/dist/services/realtimeService.d.ts +30 -2
  33. package/dist/services/row-pipeline.d.ts +12 -1
  34. package/dist/utils/pg-error-utils.d.ts +3 -1
  35. package/dist/utils/pg-numeric-number-patch.d.ts +31 -0
  36. package/package.json +32 -27
  37. package/dist/backup-service-DCk7KhhL.js +0 -9044
  38. package/dist/backup-service-DCk7KhhL.js.map +0 -1
  39. package/dist/collection-index-BRUg10H5.js +0 -5690
  40. package/dist/collection-index-BRUg10H5.js.map +0 -1
  41. package/dist/connection-GOKU3Hu5.js +0 -277
  42. package/dist/connection-GOKU3Hu5.js.map +0 -1
  43. package/dist/ensure-collection-tables-DgVixhX3.js.map +0 -1
  44. package/dist/rls-bootstrap-sql-B5C9LoJ6.js +0 -244
  45. package/dist/rls-bootstrap-sql-B5C9LoJ6.js.map +0 -1
  46. package/dist/rls-enforcement-DvAbL9YJ.js.map +0 -1
  47. package/dist/src-DiB5RP2Z.js +0 -541
  48. package/dist/src-DiB5RP2Z.js.map +0 -1
  49. package/dist/websocket-BZ4H5wUz.js +0 -8198
  50. package/dist/websocket-BZ4H5wUz.js.map +0 -1
  51. package/src/PostgresAdapter.ts +0 -91
  52. package/src/PostgresBackendDriver.ts +0 -1857
  53. package/src/PostgresBootstrapper.ts +0 -1157
  54. package/src/auth/ensure-tables.ts +0 -896
  55. package/src/auth/schema-version.ts +0 -260
  56. package/src/auth/services.ts +0 -1610
  57. package/src/backup/backup-cli.ts +0 -520
  58. package/src/backup/backup-cron.ts +0 -212
  59. package/src/backup/backup-logic.ts +0 -93
  60. package/src/backup/backup-service.ts +0 -485
  61. package/src/backup/index.ts +0 -12
  62. package/src/backup/pg-tools.ts +0 -403
  63. package/src/backup/retention.ts +0 -86
  64. package/src/cli-errors.ts +0 -265
  65. package/src/cli-helpers.ts +0 -569
  66. package/src/cli-output.ts +0 -43
  67. package/src/cli.ts +0 -1339
  68. package/src/collections/PostgresCollectionRegistry.ts +0 -108
  69. package/src/collections/buildRegistry.ts +0 -68
  70. package/src/collections/validate-relations.ts +0 -402
  71. package/src/connection.ts +0 -333
  72. package/src/data-transformer.ts +0 -845
  73. package/src/databasePoolManager.ts +0 -95
  74. package/src/history/HistoryService.ts +0 -254
  75. package/src/history/ensure-history-table.ts +0 -53
  76. package/src/index.ts +0 -19
  77. package/src/interfaces.ts +0 -60
  78. package/src/module-dir.ts +0 -7
  79. package/src/schema/atlas-argv.ts +0 -94
  80. package/src/schema/auth-schema.ts +0 -298
  81. package/src/schema/auth-users-columns.ts +0 -131
  82. package/src/schema/carved-out-migration.ts +0 -404
  83. package/src/schema/classify-change.ts +0 -436
  84. package/src/schema/collection-index.ts +0 -427
  85. package/src/schema/collections-schema-version.ts +0 -103
  86. package/src/schema/destructive-sql.ts +0 -94
  87. package/src/schema/doctor-cli.ts +0 -66
  88. package/src/schema/doctor-policy-checks.ts +0 -105
  89. package/src/schema/doctor.ts +0 -837
  90. package/src/schema/drizzle-ddl.ts +0 -33
  91. package/src/schema/dynamic-tables.test.ts +0 -302
  92. package/src/schema/dynamic-tables.ts +0 -293
  93. package/src/schema/ensure-collection-policies.ts +0 -198
  94. package/src/schema/ensure-collection-tables.test.ts +0 -518
  95. package/src/schema/ensure-collection-tables.ts +0 -1267
  96. package/src/schema/generate-drizzle-schema-logic.ts +0 -957
  97. package/src/schema/generate-drizzle-schema.ts +0 -143
  98. package/src/schema/generate-postgres-ddl-logic.ts +0 -1339
  99. package/src/schema/generate-postgres-ddl.ts +0 -180
  100. package/src/schema/generate-schema-commit.ts +0 -267
  101. package/src/schema/generated-schema-staleness.ts +0 -284
  102. package/src/schema/introspect-db-constraints.ts +0 -385
  103. package/src/schema/introspect-db-inference.ts +0 -255
  104. package/src/schema/introspect-db-logic.ts +0 -1387
  105. package/src/schema/introspect-db-naming.ts +0 -15
  106. package/src/schema/introspect-db-project.ts +0 -78
  107. package/src/schema/introspect-db-queries.ts +0 -326
  108. package/src/schema/introspect-db-structure.ts +0 -670
  109. package/src/schema/introspect-db-types.ts +0 -56
  110. package/src/schema/introspect-db.ts +0 -257
  111. package/src/schema/introspect-runtime.test.ts +0 -260
  112. package/src/schema/introspect-runtime.ts +0 -327
  113. package/src/schema/non-sql-collections.test.ts +0 -131
  114. package/src/schema/rls-bootstrap-sql.ts +0 -288
  115. package/src/schema/search-column.ts +0 -643
  116. package/src/schema/test-schema.ts +0 -11
  117. package/src/schema/vector-index.ts +0 -491
  118. package/src/security/anonymous-grants.test.ts +0 -73
  119. package/src/security/policy-drift.test.ts +0 -435
  120. package/src/security/policy-drift.ts +0 -473
  121. package/src/security/rls-enforcement.ts +0 -505
  122. package/src/services/BranchService.ts +0 -326
  123. package/src/services/FetchService.ts +0 -2115
  124. package/src/services/PersistService.ts +0 -557
  125. package/src/services/RelationService.ts +0 -978
  126. package/src/services/RelationWriteService.ts +0 -653
  127. package/src/services/cdc/CdcListener.ts +0 -103
  128. package/src/services/cdc/junction-tables.ts +0 -91
  129. package/src/services/cdc/trigger-cdc.ts +0 -173
  130. package/src/services/channel-bus/ChannelBus.ts +0 -44
  131. package/src/services/channel-bus/PostgresChannelBus.ts +0 -299
  132. package/src/services/channel-bus/index.ts +0 -114
  133. package/src/services/channel-history.ts +0 -424
  134. package/src/services/channel-presence.ts +0 -185
  135. package/src/services/collection-helpers.ts +0 -420
  136. package/src/services/dataService.ts +0 -256
  137. package/src/services/index.ts +0 -14
  138. package/src/services/junction-writes.ts +0 -295
  139. package/src/services/nested-path.ts +0 -145
  140. package/src/services/pg-notify-listener.ts +0 -151
  141. package/src/services/realtimeService.ts +0 -2545
  142. package/src/services/row-pipeline.ts +0 -240
  143. package/src/services/write-denial.ts +0 -55
  144. package/src/types.ts +0 -4
  145. package/src/utils/connection-string.ts +0 -58
  146. package/src/utils/drizzle-conditions.ts +0 -2743
  147. package/src/utils/pg-array-null-patch.ts +0 -42
  148. package/src/utils/pg-error-utils.ts +0 -391
  149. package/src/websocket.ts +0 -855
@@ -1,212 +0,0 @@
1
- /**
2
- * Scheduled backups, wired into the server cron system.
3
- *
4
- * A backend enables nightly (or custom-schedule) backups by dropping a cron
5
- * file that default-exports {@link createBackupCron}. The heavy lifting —
6
- * dump, upload, prune — reuses the same primitives as the CLI so behaviour
7
- * is identical between a manual `rebase db backup` and an automated run.
8
- */
9
- import fs from "fs";
10
- import type { CronJobDefinition } from "@rebasepro/types";
11
- import type { StorageController } from "@rebasepro/server";
12
- import {
13
- BackupDestination,
14
- parseBackupDestination,
15
- parseDbNameFromUrl,
16
- resolveConnectionString
17
- } from "./pg-tools";
18
- // NOTE: `./backup-service` pulls in `execa` (ESM-only). It is imported
19
- // lazily inside the handler so the pure `backupCronConfigFromEnv` parser can
20
- // be unit-tested under CommonJS without loading it.
21
-
22
- export interface BackupCronConfig {
23
- /** Cron expression, e.g. `"0 3 * * *"` for 03:00 daily. */
24
- schedule: string;
25
- /** Postgres connection string. Defaults to `DATABASE_URL`. */
26
- connectionString: string;
27
- /** Where backups are written: a local path or an `s3://` / `gs://` URL. */
28
- destination: BackupDestination;
29
- /**
30
- * Storage controller used for `s3`/`gcs` destinations. Reuse the one the
31
- * backend already configured (S3/GCS/Local StorageController). Not needed
32
- * for local destinations.
33
- */
34
- storage?: StorageController;
35
- /** Delete backups older than this many days. `0` disables pruning. */
36
- retentionDays?: number;
37
- /** Always keep at least this many recent backups regardless of age. */
38
- keepMinimum?: number;
39
- /** Schemas to exclude from the dump (defaults to Atlas revision schema). */
40
- excludeSchemas?: string[];
41
- /** Cron job display name. */
42
- name?: string;
43
- /** Whether the job is enabled. */
44
- enabled?: boolean;
45
- }
46
-
47
- export interface EnvResolution {
48
- /** Resolved config, present when a schedule is configured. */
49
- config?: Omit<BackupCronConfig, "storage">;
50
- /** True when `BACKUP_SCHEDULE` is unset — the cron should be a no-op. */
51
- disabled?: boolean;
52
- /** Human-readable reason the config could not be built. */
53
- error?: string;
54
- }
55
-
56
- /**
57
- * Build backup-cron config purely from environment variables.
58
- * Recognised keys:
59
- * - `BACKUP_SCHEDULE` cron expression (required to enable)
60
- * - `BACKUP_DESTINATION` local path or `s3://` / `gs://` URL (required)
61
- * - `BACKUP_RETENTION_DAYS` integer days (optional)
62
- * - `BACKUP_KEEP_MINIMUM` integer count (optional)
63
- * - `DATABASE_URL` connection string
64
- *
65
- * Pure and side-effect free so it can be unit-tested without a database.
66
- */
67
- export function backupCronConfigFromEnv(env: Record<string, string | undefined>): EnvResolution {
68
- const schedule = env.BACKUP_SCHEDULE?.trim();
69
- if (!schedule) {
70
- return { disabled: true };
71
- }
72
-
73
- const connectionString = resolveConnectionString(env);
74
- if (!connectionString) {
75
- return { error: "BACKUP_SCHEDULE is set but DATABASE_URL is not configured." };
76
- }
77
-
78
- const destinationRaw = env.BACKUP_DESTINATION?.trim();
79
- if (!destinationRaw) {
80
- return { error: "BACKUP_SCHEDULE is set but BACKUP_DESTINATION is not configured." };
81
- }
82
- const destination = parseBackupDestination(destinationRaw);
83
-
84
- const retentionDays = parseOptionalInt(env.BACKUP_RETENTION_DAYS);
85
- if (retentionDays === "invalid") {
86
- return { error: `BACKUP_RETENTION_DAYS must be an integer, got "${env.BACKUP_RETENTION_DAYS}".` };
87
- }
88
- const keepMinimum = parseOptionalInt(env.BACKUP_KEEP_MINIMUM);
89
- if (keepMinimum === "invalid") {
90
- return { error: `BACKUP_KEEP_MINIMUM must be an integer, got "${env.BACKUP_KEEP_MINIMUM}".` };
91
- }
92
-
93
- return {
94
- config: {
95
- schedule,
96
- connectionString,
97
- destination,
98
- retentionDays: retentionDays ?? undefined,
99
- keepMinimum: keepMinimum ?? undefined
100
- }
101
- };
102
- }
103
-
104
- function parseOptionalInt(value: string | undefined): number | null | "invalid" {
105
- if (value === undefined || value.trim() === "") return null;
106
- const n = Number(value);
107
- if (!Number.isInteger(n) || n < 0) return "invalid";
108
- return n;
109
- }
110
-
111
- /**
112
- * Create a {@link CronJobDefinition} that dumps the database, uploads the
113
- * result to the configured destination, and prunes old backups. Object
114
- * destinations require {@link BackupCronConfig.storage}.
115
- */
116
- export function createBackupCron(config: BackupCronConfig): CronJobDefinition {
117
- const dbName = parseDbNameFromUrl(config.connectionString) ?? "database";
118
- const excludeSchemas = config.excludeSchemas ?? ["rebase"];
119
-
120
- return {
121
- name: config.name ?? "Scheduled database backup",
122
- schedule: config.schedule,
123
- description: "Dumps the Postgres database and uploads it to the configured backup destination.",
124
- enabled: config.enabled ?? true,
125
- // Backups of a large database can take a while; allow up to an hour.
126
- timeoutSeconds: 3600,
127
- async handler({ log }) {
128
- const { createDump, pruneBackups, uploadBackup, validateDump } = await import("./backup-service");
129
- const { destination } = config;
130
-
131
- if (destination.kind !== "local" && !config.storage) {
132
- throw new Error(
133
- `Backup destination is ${destination.kind} but no storage controller was provided. ` +
134
- "Pass the backend's configured StorageController to createBackupCron({ storage })."
135
- );
136
- }
137
-
138
- log(`Starting backup of "${dbName}"…`);
139
- const outDir = destination.kind === "local" ? destination.path : undefined;
140
- const dump = await createDump({
141
- connectionString: config.connectionString,
142
- dbName,
143
- outDir,
144
- excludeSchemas
145
- });
146
- log(`Dump created: ${dump.fileName} (${formatBytes(dump.sizeBytes)})`);
147
-
148
- // Validate BEFORE pruning: a corrupt-but-exit-0 dump must never be
149
- // the reason the last good backup gets deleted.
150
- const check = await validateDump(dump.localFile);
151
- if (!check.ok) {
152
- // Clean up the bad temp file for object destinations.
153
- if (destination.kind !== "local" && fs.existsSync(dump.localFile)) {
154
- fs.unlinkSync(dump.localFile);
155
- }
156
- if (dump.globalsFile && destination.kind !== "local" && fs.existsSync(dump.globalsFile)) {
157
- fs.unlinkSync(dump.globalsFile);
158
- }
159
- throw new Error(`New backup failed validation — skipping upload and pruning to protect existing backups. ${check.reason}`);
160
- }
161
-
162
- let storedKey = dump.localFile;
163
- try {
164
- if (destination.kind !== "local") {
165
- const uploaded = await uploadBackup(config.storage!, dump.localFile, destination);
166
- storedKey = uploaded.storageUrl;
167
- log(`Uploaded to ${uploaded.storageUrl}`);
168
- // Upload the roles sidecar so a restore can recreate the
169
- // roles the dump's GRANT/RLS statements depend on.
170
- if (dump.globalsFile && fs.existsSync(dump.globalsFile)) {
171
- const g = await uploadBackup(config.storage!, dump.globalsFile, destination);
172
- log(`Uploaded roles sidecar to ${g.storageUrl}`);
173
- }
174
- }
175
- } finally {
176
- // For object-storage destinations the local dump was a temp
177
- // file — remove it once uploaded (or on failure).
178
- if (destination.kind !== "local" && fs.existsSync(dump.localFile)) {
179
- fs.unlinkSync(dump.localFile);
180
- }
181
- if (dump.globalsFile && destination.kind !== "local" && fs.existsSync(dump.globalsFile)) {
182
- fs.unlinkSync(dump.globalsFile);
183
- }
184
- }
185
-
186
- let pruned: string[] = [];
187
- if (config.retentionDays && config.retentionDays > 0) {
188
- pruned = await pruneBackups(
189
- destination,
190
- { retentionDays: config.retentionDays, keepMinimum: config.keepMinimum },
191
- config.storage
192
- );
193
- if (pruned.length > 0) {
194
- log(`Pruned ${pruned.length} backup(s) older than ${config.retentionDays} day(s).`);
195
- }
196
- }
197
-
198
- return {
199
- backup: storedKey,
200
- sizeBytes: dump.sizeBytes,
201
- pruned: pruned.length
202
- };
203
- }
204
- };
205
- }
206
-
207
- function formatBytes(bytes: number): string {
208
- if (bytes < 1024) return `${bytes} B`;
209
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
210
- if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
211
- return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
212
- }
@@ -1,93 +0,0 @@
1
- /**
2
- * Pure-ish orchestration for the backup/restore paths, kept free of `execa`
3
- * and `pg` value imports so it can be unit-tested under jest (the impure
4
- * edges — spawning processes, opening connections — live in
5
- * `backup-service.ts`, which is vitest/runtime only).
6
- *
7
- * The functions here take their side-effecting dependency as an argument
8
- * (a statement runner, an object deleter) so tests can inject fakes.
9
- */
10
- import { globalsFileForDump, splitGlobalsStatements } from "./pg-tools";
11
-
12
- /**
13
- * Replay a `pg_dumpall --globals-only` script one statement at a time,
14
- * tolerating per-statement failures. On a same-cluster restore the roles
15
- * usually already exist (`CREATE ROLE` → "already exists") and on a managed
16
- * provider an `ALTER ROLE <superuser>` may be refused; neither should abort
17
- * recreation of the roles that *are* missing. Returns how many statements
18
- * applied vs were skipped.
19
- *
20
- * `runStatement` executes one SQL statement and rejects on error.
21
- */
22
- export async function applyGlobalsWith(
23
- runStatement: (sql: string) => Promise<void>,
24
- globalsSql: string,
25
- log: (message: string) => void = () => {}
26
- ): Promise<{ applied: number; skipped: number }> {
27
- let applied = 0;
28
- let skipped = 0;
29
- for (const statement of splitGlobalsStatements(globalsSql)) {
30
- try {
31
- await runStatement(statement);
32
- applied++;
33
- } catch (err) {
34
- skipped++;
35
- const firstLine = statement.split("\n")[0].slice(0, 80);
36
- log(` • Skipped global: ${firstLine} (${err instanceof Error ? err.message : String(err)})`);
37
- }
38
- }
39
- return { applied, skipped };
40
- }
41
-
42
- /**
43
- * Delete each pruned dump together with its `.globals.sql` roles sidecar, so
44
- * pruning never orphans the roles file. The sidecar is best-effort — older
45
- * backups predate it, so a missing-sidecar failure is swallowed while a
46
- * failure deleting the dump itself propagates.
47
- *
48
- * `deleteObject` removes one key and rejects if it cannot (e.g. not found).
49
- */
50
- export async function pruneWith(
51
- keys: string[],
52
- deleteObject: (key: string) => Promise<void>
53
- ): Promise<void> {
54
- for (const key of keys) {
55
- await deleteObject(key);
56
- try {
57
- await deleteObject(globalsFileForDump(key));
58
- } catch {
59
- // Sidecar may not exist for older backups — ignore.
60
- }
61
- }
62
- }
63
-
64
- /**
65
- * Remove a dump artifact abandoned by a failed tool run.
66
- *
67
- * `pg_dump` creates its `--file=` target before it finishes connecting, so any
68
- * failure — a URL libpq rejects, a dropped connection, a full disk — leaves a
69
- * 0-byte file behind. That corpse is not inert: `rebase db backups list` used
70
- * to show it as an ordinary entry, and `selectBackupsToPrune` ranks by
71
- * timestamp alone, so it occupies a protected `keepMinimum` slot and can push
72
- * a real backup out of retention.
73
- *
74
- * Best-effort on purpose: the caller is already throwing, and a failed unlink
75
- * must not replace the real diagnosis with an ENOENT/EPERM from the cleanup.
76
- *
77
- * Lives here rather than in `backup-service.ts` for the reason at the top of
78
- * this file — that module's top-level `execa` import makes it unloadable under
79
- * jest, so anything placed there cannot be unit-tested.
80
- *
81
- * `remove` deletes one file and throws if it cannot; `exists` reports presence.
82
- */
83
- export function discardPartialDumpWith(
84
- exists: (file: string) => boolean,
85
- remove: (file: string) => void,
86
- file: string
87
- ): void {
88
- try {
89
- if (exists(file)) remove(file);
90
- } catch {
91
- // Ignored: see above.
92
- }
93
- }