@rebasepro/server-postgres 0.17.3 → 0.18.1

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 (204) hide show
  1. package/LICENSE +0 -1
  2. package/README.md +31 -0
  3. package/dist/BranchService-ByLfM_Tt.js +360 -0
  4. package/dist/BranchService-ByLfM_Tt.js.map +1 -0
  5. package/dist/__vite-browser-external-BnuHet1e.js +46 -0
  6. package/dist/__vite-browser-external-BnuHet1e.js.map +1 -0
  7. package/dist/arg-Dni7MzLB.js +87 -0
  8. package/dist/arg-Dni7MzLB.js.map +1 -0
  9. package/dist/auth-users-columns-D2LBFrMH.js +116 -0
  10. package/dist/auth-users-columns-D2LBFrMH.js.map +1 -0
  11. package/dist/backup-argv.d.ts +19 -0
  12. package/dist/backup-cli-oM9WQVlV.js +408 -0
  13. package/dist/backup-cli-oM9WQVlV.js.map +1 -0
  14. package/dist/backup-service-Br3tqwsy.js +715 -0
  15. package/dist/backup-service-Br3tqwsy.js.map +1 -0
  16. package/dist/branch-argv.d.ts +28 -0
  17. package/dist/branch-prune.d.ts +77 -0
  18. package/dist/cli-collections-path.d.ts +31 -0
  19. package/dist/cli-errors-DxN4yzdH.js +657 -0
  20. package/dist/cli-errors-DxN4yzdH.js.map +1 -0
  21. package/dist/cli-errors.d.ts +123 -0
  22. package/dist/cli-flags.d.ts +101 -0
  23. package/dist/cli-helpers.d.ts +94 -10
  24. package/dist/cli-output-CNdMql-L.js +47 -0
  25. package/dist/cli-output-CNdMql-L.js.map +1 -0
  26. package/dist/cli.js +2020 -0
  27. package/dist/cli.js.map +1 -0
  28. package/dist/collection-index-BHgT_TKd.js +276 -0
  29. package/dist/collection-index-BHgT_TKd.js.map +1 -0
  30. package/dist/{collections-schema-version-BMeu3cgv.js → collections-schema-version-T6cGvrDi.js} +1 -2
  31. package/dist/{collections-schema-version-BMeu3cgv.js.map → collections-schema-version-T6cGvrDi.js.map} +1 -1
  32. package/dist/{connection-GOKU3Hu5.js → databasePoolManager-Bj5FbeAs.js} +112 -40
  33. package/dist/databasePoolManager-Bj5FbeAs.js.map +1 -0
  34. package/dist/databasePoolManager.d.ts +27 -0
  35. package/dist/doctor-D7HWmGzs.js +974 -0
  36. package/dist/doctor-D7HWmGzs.js.map +1 -0
  37. package/dist/{ensure-collection-policies-UCqgv_8c.js → ensure-collection-policies-2c2NV8zX.js} +7 -27
  38. package/dist/ensure-collection-policies-2c2NV8zX.js.map +1 -0
  39. package/dist/ensure-collection-tables-D4wJzM4y.js +820 -0
  40. package/dist/ensure-collection-tables-D4wJzM4y.js.map +1 -0
  41. package/dist/ensure-tables-CIojK-Rx.js +650 -0
  42. package/dist/ensure-tables-CIojK-Rx.js.map +1 -0
  43. package/dist/generate-drizzle-schema-CwEgVyjO.js +123 -0
  44. package/dist/generate-drizzle-schema-CwEgVyjO.js.map +1 -0
  45. package/dist/generate-drizzle-schema-logic-so2Z_lyC.js +528 -0
  46. package/dist/generate-drizzle-schema-logic-so2Z_lyC.js.map +1 -0
  47. package/dist/{ensure-collection-tables-DgVixhX3.js → generate-postgres-ddl-logic-CtnpIBVv.js} +126 -984
  48. package/dist/generate-postgres-ddl-logic-CtnpIBVv.js.map +1 -0
  49. package/dist/generated-schema-staleness-DQdfJl2A.js +157 -0
  50. package/dist/generated-schema-staleness-DQdfJl2A.js.map +1 -0
  51. package/dist/index.d.ts +12 -0
  52. package/dist/index.es.js +7423 -1133
  53. package/dist/index.es.js.map +1 -1
  54. package/dist/introspect-db-logic-C6LQdTxj.js +1378 -0
  55. package/dist/introspect-db-logic-C6LQdTxj.js.map +1 -0
  56. package/dist/policy-drift-kNJ6X2Uj.js +313 -0
  57. package/dist/policy-drift-kNJ6X2Uj.js.map +1 -0
  58. package/dist/{backup-service-DCk7KhhL.js → rls-bootstrap-sql-DLDnUjtx.js} +737 -2689
  59. package/dist/rls-bootstrap-sql-DLDnUjtx.js.map +1 -0
  60. package/dist/{rls-enforcement-DvAbL9YJ.js → rls-enforcement-DeYEjYLt.js} +65 -190
  61. package/dist/rls-enforcement-DeYEjYLt.js.map +1 -0
  62. package/dist/schema/bin/generate-drizzle-schema.d.ts +1 -0
  63. package/dist/schema/bin/generate-postgres-ddl.d.ts +1 -0
  64. package/dist/schema/column-type-drift.d.ts +79 -0
  65. package/dist/schema/doctor-cli.js +129 -0
  66. package/dist/schema/doctor-cli.js.map +1 -0
  67. package/dist/schema/doctor.d.ts +42 -0
  68. package/dist/schema/ensure-collection-tables.d.ts +90 -0
  69. package/dist/schema/generate-drizzle-schema.d.ts +1 -1
  70. package/dist/schema/generate-drizzle-schema.js +24 -0
  71. package/dist/schema/generate-drizzle-schema.js.map +1 -0
  72. package/dist/schema/generate-next-step.d.ts +30 -0
  73. package/dist/schema/generate-postgres-ddl-logic.d.ts +28 -0
  74. package/dist/schema/generate-postgres-ddl.d.ts +1 -1
  75. package/dist/schema/generate-postgres-ddl.js +138 -0
  76. package/dist/schema/generate-postgres-ddl.js.map +1 -0
  77. package/dist/schema/generated-schema-staleness.d.ts +37 -0
  78. package/dist/schema/introspect-db-queries.d.ts +34 -0
  79. package/dist/schema/introspect-db.js +866 -0
  80. package/dist/schema/introspect-db.js.map +1 -0
  81. package/dist/security/rls-enforcement.d.ts +63 -2
  82. package/dist/services/BranchService.d.ts +86 -1
  83. package/dist/services/collection-helpers.d.ts +1 -1
  84. package/dist/services/realtimeService.d.ts +30 -2
  85. package/dist/services/row-pipeline.d.ts +12 -1
  86. package/dist/source-Br7L7GOI.js +410 -0
  87. package/dist/source-Br7L7GOI.js.map +1 -0
  88. package/dist/utils/pg-error-utils.d.ts +3 -1
  89. package/dist/utils/pg-numeric-number-patch.d.ts +31 -0
  90. package/dist/vector-index-DkWxm2de.js +309 -0
  91. package/dist/vector-index-DkWxm2de.js.map +1 -0
  92. package/package.json +32 -27
  93. package/dist/backup-service-DCk7KhhL.js.map +0 -1
  94. package/dist/collection-index-BRUg10H5.js +0 -5690
  95. package/dist/collection-index-BRUg10H5.js.map +0 -1
  96. package/dist/connection-GOKU3Hu5.js.map +0 -1
  97. package/dist/ensure-collection-policies-UCqgv_8c.js.map +0 -1
  98. package/dist/ensure-collection-tables-DgVixhX3.js.map +0 -1
  99. package/dist/rls-bootstrap-sql-B5C9LoJ6.js +0 -244
  100. package/dist/rls-bootstrap-sql-B5C9LoJ6.js.map +0 -1
  101. package/dist/rls-enforcement-DvAbL9YJ.js.map +0 -1
  102. package/dist/src-DiB5RP2Z.js +0 -541
  103. package/dist/src-DiB5RP2Z.js.map +0 -1
  104. package/dist/websocket-BZ4H5wUz.js +0 -8198
  105. package/dist/websocket-BZ4H5wUz.js.map +0 -1
  106. package/src/PostgresAdapter.ts +0 -91
  107. package/src/PostgresBackendDriver.ts +0 -1857
  108. package/src/PostgresBootstrapper.ts +0 -1157
  109. package/src/auth/ensure-tables.ts +0 -896
  110. package/src/auth/schema-version.ts +0 -260
  111. package/src/auth/services.ts +0 -1610
  112. package/src/backup/backup-cli.ts +0 -520
  113. package/src/backup/backup-cron.ts +0 -212
  114. package/src/backup/backup-logic.ts +0 -93
  115. package/src/backup/backup-service.ts +0 -485
  116. package/src/backup/index.ts +0 -12
  117. package/src/backup/pg-tools.ts +0 -403
  118. package/src/backup/retention.ts +0 -86
  119. package/src/cli-errors.ts +0 -265
  120. package/src/cli-helpers.ts +0 -569
  121. package/src/cli-output.ts +0 -43
  122. package/src/cli.ts +0 -1339
  123. package/src/collections/PostgresCollectionRegistry.ts +0 -108
  124. package/src/collections/buildRegistry.ts +0 -68
  125. package/src/collections/validate-relations.ts +0 -402
  126. package/src/connection.ts +0 -333
  127. package/src/data-transformer.ts +0 -845
  128. package/src/databasePoolManager.ts +0 -95
  129. package/src/history/HistoryService.ts +0 -254
  130. package/src/history/ensure-history-table.ts +0 -53
  131. package/src/index.ts +0 -19
  132. package/src/interfaces.ts +0 -60
  133. package/src/module-dir.ts +0 -7
  134. package/src/schema/atlas-argv.ts +0 -94
  135. package/src/schema/auth-schema.ts +0 -298
  136. package/src/schema/auth-users-columns.ts +0 -131
  137. package/src/schema/carved-out-migration.ts +0 -404
  138. package/src/schema/classify-change.ts +0 -436
  139. package/src/schema/collection-index.ts +0 -427
  140. package/src/schema/collections-schema-version.ts +0 -103
  141. package/src/schema/destructive-sql.ts +0 -94
  142. package/src/schema/doctor-cli.ts +0 -66
  143. package/src/schema/doctor-policy-checks.ts +0 -105
  144. package/src/schema/doctor.ts +0 -837
  145. package/src/schema/drizzle-ddl.ts +0 -33
  146. package/src/schema/dynamic-tables.test.ts +0 -302
  147. package/src/schema/dynamic-tables.ts +0 -293
  148. package/src/schema/ensure-collection-policies.ts +0 -198
  149. package/src/schema/ensure-collection-tables.test.ts +0 -518
  150. package/src/schema/ensure-collection-tables.ts +0 -1267
  151. package/src/schema/generate-drizzle-schema-logic.ts +0 -957
  152. package/src/schema/generate-drizzle-schema.ts +0 -143
  153. package/src/schema/generate-postgres-ddl-logic.ts +0 -1339
  154. package/src/schema/generate-postgres-ddl.ts +0 -180
  155. package/src/schema/generate-schema-commit.ts +0 -267
  156. package/src/schema/generated-schema-staleness.ts +0 -284
  157. package/src/schema/introspect-db-constraints.ts +0 -385
  158. package/src/schema/introspect-db-inference.ts +0 -255
  159. package/src/schema/introspect-db-logic.ts +0 -1387
  160. package/src/schema/introspect-db-naming.ts +0 -15
  161. package/src/schema/introspect-db-project.ts +0 -78
  162. package/src/schema/introspect-db-queries.ts +0 -326
  163. package/src/schema/introspect-db-structure.ts +0 -670
  164. package/src/schema/introspect-db-types.ts +0 -56
  165. package/src/schema/introspect-db.ts +0 -257
  166. package/src/schema/introspect-runtime.test.ts +0 -260
  167. package/src/schema/introspect-runtime.ts +0 -327
  168. package/src/schema/non-sql-collections.test.ts +0 -131
  169. package/src/schema/rls-bootstrap-sql.ts +0 -288
  170. package/src/schema/search-column.ts +0 -643
  171. package/src/schema/test-schema.ts +0 -11
  172. package/src/schema/vector-index.ts +0 -491
  173. package/src/security/anonymous-grants.test.ts +0 -73
  174. package/src/security/policy-drift.test.ts +0 -435
  175. package/src/security/policy-drift.ts +0 -473
  176. package/src/security/rls-enforcement.ts +0 -505
  177. package/src/services/BranchService.ts +0 -326
  178. package/src/services/FetchService.ts +0 -2115
  179. package/src/services/PersistService.ts +0 -557
  180. package/src/services/RelationService.ts +0 -978
  181. package/src/services/RelationWriteService.ts +0 -653
  182. package/src/services/cdc/CdcListener.ts +0 -103
  183. package/src/services/cdc/junction-tables.ts +0 -91
  184. package/src/services/cdc/trigger-cdc.ts +0 -173
  185. package/src/services/channel-bus/ChannelBus.ts +0 -44
  186. package/src/services/channel-bus/PostgresChannelBus.ts +0 -299
  187. package/src/services/channel-bus/index.ts +0 -114
  188. package/src/services/channel-history.ts +0 -424
  189. package/src/services/channel-presence.ts +0 -185
  190. package/src/services/collection-helpers.ts +0 -420
  191. package/src/services/dataService.ts +0 -256
  192. package/src/services/index.ts +0 -14
  193. package/src/services/junction-writes.ts +0 -295
  194. package/src/services/nested-path.ts +0 -145
  195. package/src/services/pg-notify-listener.ts +0 -151
  196. package/src/services/realtimeService.ts +0 -2545
  197. package/src/services/row-pipeline.ts +0 -240
  198. package/src/services/write-denial.ts +0 -55
  199. package/src/types.ts +0 -4
  200. package/src/utils/connection-string.ts +0 -58
  201. package/src/utils/drizzle-conditions.ts +0 -2743
  202. package/src/utils/pg-array-null-patch.ts +0 -42
  203. package/src/utils/pg-error-utils.ts +0 -391
  204. package/src/websocket.ts +0 -855
@@ -1,95 +0,0 @@
1
- import { Pool } from "pg";
2
- import { drizzle } from "drizzle-orm/node-postgres";
3
- import { NodePgDatabase } from "drizzle-orm/node-postgres";
4
- import { logger } from "@rebasepro/server";
5
- import { guardPoolAgainstDirtyRelease, pinSearchPath, cappedPoolMax } from "./connection";
6
-
7
- export class DatabasePoolManager {
8
- private pools: Map<string, Pool> = new Map();
9
- private drizzleInstances: Map<string, NodePgDatabase> = new Map();
10
- public readonly defaultDatabaseName: string;
11
- private readonly rootConnectionString: string;
12
-
13
- constructor(adminConnectionString: string) {
14
- this.rootConnectionString = adminConnectionString;
15
- try {
16
- const url = new URL(adminConnectionString);
17
- this.defaultDatabaseName = url.pathname.slice(1);
18
- } catch (e) {
19
- throw new Error(`Invalid adminConnectionString provided: ${e}`);
20
- }
21
- }
22
-
23
- public getDrizzle(databaseName: string): NodePgDatabase<Record<string, never>> {
24
- const existing = this.drizzleInstances.get(databaseName);
25
- if (existing) {
26
- return existing;
27
- }
28
-
29
- const pool = this.getPool(databaseName);
30
- const db = drizzle(pool);
31
- this.drizzleInstances.set(databaseName, db);
32
- return db;
33
- }
34
-
35
- public getPool(databaseName: string): Pool {
36
- if (this.pools.has(databaseName)) {
37
- return this.pools.get(databaseName)!;
38
- }
39
-
40
- const url = new URL(this.rootConnectionString);
41
- url.pathname = `/${databaseName}`;
42
-
43
- const pool = new Pool({
44
- // Same pin as the primary pool: these are branch/multi-database
45
- // connections to the *same* server, so they inherit the same
46
- // `"$user"` hazard. See `pinSearchPath`.
47
- connectionString: pinSearchPath(url.toString()),
48
- // Capped by REBASE_DB_POOL_MAX, which the managed development
49
- // database sets to 1: PGlite multiplexes onto a single session and
50
- // overlapping transactions deadlock there.
51
- max: cappedPoolMax(10),
52
- idleTimeoutMillis: 10000, // Reduced from 30000 for aggressive cleanup
53
- allowExitOnIdle: true // Prevent idle clients from hanging the Node.js process
54
- });
55
-
56
- // Prevent idle client errors from crashing the Node.js process
57
- pool.on("error", (err) => {
58
- logger.error(`[DatabasePoolManager] Unexpected error on idle client for db ${databaseName}`, { error: err });
59
- });
60
- guardPoolAgainstDirtyRelease(pool, `pg-pool:${databaseName}`);
61
-
62
- this.pools.set(databaseName, pool);
63
- return pool;
64
- }
65
-
66
- /**
67
- * Disconnect and remove the pool for a specific database.
68
- * Required before `CREATE DATABASE ... TEMPLATE` or `DROP DATABASE`,
69
- * which need exclusive access to the target database.
70
- */
71
- public async disconnectDatabase(databaseName: string): Promise<void> {
72
- const pool = this.pools.get(databaseName);
73
- if (pool) {
74
- await pool.end();
75
- this.pools.delete(databaseName);
76
- this.drizzleInstances.delete(databaseName);
77
- }
78
- }
79
-
80
- /** Check if a pool exists for a given database name. */
81
- public hasPool(databaseName: string): boolean {
82
- return this.pools.has(databaseName);
83
- }
84
-
85
- public async shutdown(): Promise<void> {
86
- const promises = [];
87
- for (const [dbName, pool] of this.pools.entries()) {
88
- logger.info(`[DatabasePoolManager] Shutting down pool for ${dbName}`);
89
- promises.push(pool.end());
90
- }
91
- await Promise.all(promises);
92
- this.pools.clear();
93
- this.drizzleInstances.clear();
94
- }
95
- }
@@ -1,254 +0,0 @@
1
- import { sql } from "drizzle-orm";
2
- import { NodePgDatabase } from "drizzle-orm/node-postgres";
3
- import { logger } from "@rebasepro/server";
4
- import type { EntityHistoryEntry } from "@rebasepro/types";
5
-
6
- export type {
7
- RecordHistoryParams,
8
- FetchHistoryOptions,
9
- HistoryRetentionConfig
10
- } from "@rebasepro/types";
11
- import type { RecordHistoryParams, FetchHistoryOptions, HistoryRetentionConfig } from "@rebasepro/types";
12
-
13
- /**
14
- * A Postgres history row is already the wire shape — `updated_at` comes back
15
- * from the driver as a string. Kept as an alias because the name is used
16
- * throughout this package and in `PostgresBackendDriver`.
17
- */
18
- export type HistoryEntry = EntityHistoryEntry;
19
-
20
- const DEFAULT_RETENTION: HistoryRetentionConfig = {
21
- maxEntries: 200,
22
- ttlDays: 90
23
- };
24
-
25
- /**
26
- * Service for recording and querying row change history.
27
- * Stores history entries in the `rebase.entity_history` table.
28
- */
29
- export class HistoryService {
30
- public retention: HistoryRetentionConfig;
31
-
32
- constructor(
33
- private db: NodePgDatabase,
34
- retention?: Partial<HistoryRetentionConfig>
35
- ) {
36
- this.retention = { ...DEFAULT_RETENTION,
37
- ...retention };
38
- }
39
-
40
- /**
41
- * Record a history entry for an row change.
42
- * This is intentionally fire-and-forget safe — errors are logged but never
43
- * bubble up to block the main save/delete operation.
44
- *
45
- * After inserting, kicks off a non-blocking pruning pass for this row.
46
- */
47
- async recordHistory(params: RecordHistoryParams): Promise<void> {
48
- const {
49
- tableName,
50
- id,
51
- action,
52
- values,
53
- previousValues,
54
- updatedBy
55
- } = params;
56
-
57
- const changedFields = previousValues && values
58
- ? findChangedFields(previousValues, values)
59
- : null;
60
-
61
-
62
- // Skip recording if this is an update with zero actual changes
63
-
64
- if (action === "update" && (!changedFields || changedFields.length === 0)) {
65
- return;
66
- }
67
-
68
- try {
69
- await this.db.execute(sql`
70
- INSERT INTO rebase.entity_history
71
- (table_name, entity_id, action, changed_fields, "values", previous_values, updated_by)
72
- VALUES (
73
- ${tableName},
74
- ${String(id)},
75
- ${action},
76
- ${changedFields ? sql`ARRAY[${sql.join(changedFields.map(f => sql`${f}`), sql`, `)}]::text[]` : sql`NULL`},
77
- ${values ? sql`${JSON.stringify(values)}::jsonb` : sql`NULL`},
78
- ${previousValues ? sql`${JSON.stringify(previousValues)}::jsonb` : sql`NULL`},
79
- ${updatedBy ?? null}
80
- )
81
- `);
82
-
83
- // Non-blocking prune for this specific row
84
- this.pruneEntity(tableName, id).catch(err =>
85
- logger.error("History prune failed", { error: err })
86
- );
87
- } catch (error) {
88
- logger.error("Failed to record row history", { error: error });
89
- }
90
- }
91
-
92
- /**
93
- * Fetch history entries for an row, ordered by most recent first.
94
- */
95
- async fetchHistory(
96
- tableName: string,
97
- id: string,
98
- options: FetchHistoryOptions = {}
99
- ): Promise<{ data: HistoryEntry[]; total: number }> {
100
- const limit = options.limit ?? 20;
101
- const offset = options.offset ?? 0;
102
-
103
- const [countResult, dataResult] = await Promise.all([
104
- this.db.execute(sql`
105
- SELECT COUNT(*) as count
106
- FROM rebase.entity_history
107
- WHERE table_name = ${tableName}
108
- AND entity_id = ${String(id)}
109
- `),
110
- this.db.execute(sql`
111
- SELECT id, table_name, entity_id, action, changed_fields,
112
- "values", previous_values, updated_by, updated_at
113
- FROM rebase.entity_history
114
- WHERE table_name = ${tableName}
115
- AND entity_id = ${String(id)}
116
- ORDER BY updated_at DESC
117
- LIMIT ${limit}
118
- OFFSET ${offset}
119
- `)
120
- ]);
121
-
122
- const total = parseInt(
123
- (countResult.rows[0] as Record<string, string>)?.count ?? "0",
124
- 10
125
- );
126
-
127
- return {
128
- data: dataResult.rows as unknown as HistoryEntry[],
129
- total
130
- };
131
- }
132
-
133
- /**
134
- * Fetch a single history entry by ID.
135
- */
136
- async fetchHistoryEntry(historyId: string): Promise<HistoryEntry | null> {
137
- const result = await this.db.execute(sql`
138
- SELECT id, table_name, entity_id, action, changed_fields,
139
- "values", previous_values, updated_by, updated_at
140
- FROM rebase.entity_history
141
- WHERE id = ${historyId}
142
- `);
143
-
144
- if (result.rows.length === 0) return null;
145
- return result.rows[0] as unknown as HistoryEntry;
146
- }
147
-
148
- // ───────── Retention / Pruning ─────────
149
-
150
- /**
151
- * Prune history for a single row: enforce maxEntries and TTL.
152
- */
153
- async pruneEntity(tableName: string, id: string): Promise<number> {
154
- let deleted = 0;
155
-
156
- // 1. TTL — delete entries older than ttlDays
157
- const ttlResult = await this.db.execute(sql`
158
- DELETE FROM rebase.entity_history
159
- WHERE table_name = ${tableName}
160
- AND entity_id = ${String(id)}
161
- AND updated_at < NOW() - MAKE_INTERVAL(days => ${this.retention.ttlDays})
162
- `);
163
- deleted += ttlResult.rowCount ?? 0;
164
-
165
- // 2. Max entries — keep the newest maxEntries, delete the rest
166
- const maxResult = await this.db.execute(sql`
167
- DELETE FROM rebase.entity_history
168
- WHERE id IN (
169
- SELECT id FROM rebase.entity_history
170
- WHERE table_name = ${tableName}
171
- AND entity_id = ${String(id)}
172
- ORDER BY updated_at DESC
173
- OFFSET ${this.retention.maxEntries}
174
- )
175
- `);
176
- deleted += maxResult.rowCount ?? 0;
177
-
178
- return deleted;
179
- }
180
-
181
- /**
182
- * Global prune: enforce TTL across ALL rows in a single sweep.
183
- * Intended to be called periodically (e.g. once per hour or daily).
184
- */
185
- async pruneExpired(): Promise<number> {
186
- const result = await this.db.execute(sql`
187
- DELETE FROM rebase.entity_history
188
- WHERE updated_at < NOW() - MAKE_INTERVAL(days => ${this.retention.ttlDays})
189
- `);
190
- return result.rowCount ?? 0;
191
- }
192
- }
193
-
194
-
195
- /**
196
- * Deep equality without JSON.stringify.
197
- * Handles primitives, arrays, Dates, and plain objects recursively.
198
- */
199
- function deepEqual(a: unknown, b: unknown): boolean {
200
- if (a === b) return true;
201
- if (a == null || b == null) return false;
202
- if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
203
- if (Array.isArray(a) && Array.isArray(b)) {
204
- if (a.length !== b.length) return false;
205
- return a.every((v, i) => deepEqual(v, b[i]));
206
- }
207
- if (typeof a === "object" && typeof b === "object") {
208
- const aObj = a as Record<string, unknown>;
209
- const bObj = b as Record<string, unknown>;
210
- const aKeys = Object.keys(aObj);
211
- const bKeys = Object.keys(bObj);
212
- if (aKeys.length !== bKeys.length) return false;
213
- return aKeys.every(k => deepEqual(aObj[k], bObj[k]));
214
- }
215
- return false;
216
- }
217
-
218
- /**
219
- * Shallow comparison to find top-level keys that changed between two objects.
220
- */
221
- export function findChangedFields(
222
- oldValues: Record<string, unknown>,
223
- newValues: Record<string, unknown>
224
- ): string[] | null {
225
- const changed: string[] = [];
226
- const allKeys = new Set([
227
- ...Object.keys(oldValues),
228
- ...Object.keys(newValues)
229
- ]);
230
-
231
- for (const key of allKeys) {
232
- const oldVal = oldValues[key];
233
- const newVal = newValues[key];
234
-
235
- // Skip internal metadata
236
- if (key.startsWith("__")) continue;
237
-
238
- if (oldVal !== newVal) {
239
- // For objects/arrays, use structural comparison
240
- if (
241
- typeof oldVal === "object" && oldVal !== null &&
242
- typeof newVal === "object" && newVal !== null
243
- ) {
244
- if (!deepEqual(oldVal, newVal)) {
245
- changed.push(key);
246
- }
247
- } else {
248
- changed.push(key);
249
- }
250
- }
251
- }
252
-
253
- return changed.length > 0 ? changed : null;
254
- }
@@ -1,53 +0,0 @@
1
- import { sql } from "drizzle-orm";
2
- import { NodePgDatabase } from "drizzle-orm/node-postgres";
3
- import { logger } from "@rebasepro/server";
4
- import { revokeInternalTableSql } from "@rebasepro/common";
5
-
6
- /**
7
- * Auto-create the row history table if it doesn't exist.
8
- * This runs on startup when history is enabled, following the same
9
- * pattern as `ensureAuthTablesExist`.
10
- */
11
- export async function ensureHistoryTableExists(db: NodePgDatabase): Promise<void> {
12
- logger.debug("🔍 Checking row history table...");
13
-
14
- try {
15
- // Create the rebase schema (idempotent — may already exist from auth init)
16
- await db.execute(sql`CREATE SCHEMA IF NOT EXISTS rebase`);
17
-
18
- await db.execute(sql`
19
- CREATE TABLE IF NOT EXISTS rebase.entity_history (
20
- id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
21
- table_name TEXT NOT NULL,
22
- entity_id TEXT NOT NULL,
23
- action TEXT NOT NULL,
24
- changed_fields TEXT[],
25
- "values" JSONB,
26
- previous_values JSONB,
27
- updated_by TEXT,
28
- updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
29
- )
30
- `);
31
-
32
- await db.execute(sql`
33
- CREATE INDEX IF NOT EXISTS idx_history_entity
34
- ON rebase.entity_history(table_name, entity_id)
35
- `);
36
-
37
- await db.execute(sql`
38
- CREATE INDEX IF NOT EXISTS idx_history_time
39
- ON rebase.entity_history(table_name, entity_id, updated_at DESC)
40
- `);
41
-
42
- // Every previous value of every audited row, in one table with no RLS
43
- // and no tenant scoping — so a readable copy defeats the row policies on
44
- // the tables it shadows. The driver's schema-wide grant reaches it
45
- // (created here, after that grant ran), so take it back.
46
- await db.execute(sql.raw(revokeInternalTableSql("rebase", "entity_history")));
47
-
48
- logger.debug("✅ Entity history table ready");
49
- } catch (error) {
50
- logger.error("❌ Failed to create row history table", { error: error });
51
- logger.warn("⚠️ Continuing without creating history table.");
52
- }
53
- }
package/src/index.ts DELETED
@@ -1,19 +0,0 @@
1
- export * from "./connection";
2
- export * from "./interfaces";
3
- export * from "./PostgresBackendDriver";
4
- export * from "./databasePoolManager";
5
- export * from "./schema/auth-schema";
6
- export * from "./schema/generate-drizzle-schema-logic";
7
- export * from "./schema/generate-drizzle-schema";
8
- export * from "./utils/drizzle-conditions";
9
- export * from "./services/realtimeService";
10
- // The channel bus is a public extension point: a transport published as its own
11
- // package implements `ChannelBus` (declared in @rebasepro/types and re-exported
12
- // here) and is passed to `realtime.bus`. Without this export it would be one.
13
- export * from "./services/channel-bus";
14
- export * from "./websocket";
15
- export * from "./collections/PostgresCollectionRegistry";
16
- export * from "./services/BranchService";
17
- export * from "./backup";
18
- export * from "./PostgresBootstrapper";
19
- export * from "./PostgresAdapter";
package/src/interfaces.ts DELETED
@@ -1,60 +0,0 @@
1
- /**
2
- * Database Abstraction Interfaces
3
- *
4
- * These interfaces define the contracts that any database backend must implement
5
- * to be used with Rebase. This allows for pluggable database backends like
6
- * PostgreSQL, MongoDB, MySQL, etc.
7
- */
8
-
9
- import {
10
- Entity,
11
- CollectionConfig,
12
- FilterValues,
13
- WhereFilterOp,
14
- DatabaseConnection,
15
- QueryFilter,
16
- FetchCollectionOptions,
17
- SearchOptions,
18
- CountOptions,
19
- ConditionBuilder,
20
- ConditionBuilderStatic,
21
- DataRepository,
22
- CollectionSubscriptionConfig,
23
- SingleSubscriptionConfig,
24
- RealtimeProvider,
25
- CollectionRegistryInterface,
26
- DataTransformer,
27
- BackendConfig,
28
- BackendInstance,
29
- BackendFactory
30
- } from "@rebasepro/types";
31
- import { NodePgDatabase } from "drizzle-orm/node-postgres";
32
- import { PgTransaction } from "drizzle-orm/pg-core";
33
-
34
- /**
35
- * Type representing either a direct database connection or a transaction.
36
- * Used to allow services to operate within a transaction context.
37
- * Note: `any` is intentional here — it represents a Drizzle client with
38
- * a dynamic schema, enabling `db.query[tableName]` access without casts.
39
- */
40
-
41
- export type DrizzleClient = NodePgDatabase<any> | PgTransaction<any, any, any>;
42
-
43
- export type {
44
- DatabaseConnection,
45
- QueryFilter,
46
- FetchCollectionOptions,
47
- SearchOptions,
48
- CountOptions,
49
- ConditionBuilder,
50
- ConditionBuilderStatic,
51
- DataRepository,
52
- CollectionSubscriptionConfig,
53
- SingleSubscriptionConfig,
54
- RealtimeProvider,
55
- CollectionRegistryInterface,
56
- DataTransformer,
57
- BackendConfig,
58
- BackendInstance,
59
- BackendFactory
60
- };
package/src/module-dir.ts DELETED
@@ -1,7 +0,0 @@
1
- import path from "path";
2
- import { fileURLToPath } from "url";
3
-
4
- // Isolated in its own module so the jest CommonJS transform can swap it for a
5
- // __dirname shim via moduleNameMapper — `import.meta` is a syntax error once
6
- // ts-jest transpiles to CJS.
7
- export const moduleDir = path.dirname(fileURLToPath(import.meta.url));
@@ -1,94 +0,0 @@
1
- /**
2
- * The argv `runAtlas` hands the Atlas binary, as a pure function.
3
- *
4
- * Split out of `cli.ts` because the flags an Atlas subcommand accepts are not
5
- * uniform and getting one wrong is fatal rather than degraded: Atlas rejects an
6
- * unknown flag before doing any work, so a flag on the wrong subcommand takes
7
- * the whole command down.
8
- *
9
- * `--exclude` is the one that bit. Of the six invocations here, exactly one
10
- * accepts it. The guard this replaces was `args.includes("apply") ||
11
- * args.includes("diff")`, and reading it as a *subcommand* test is the trap:
12
- * `migrate apply` passes `args.includes("apply")` just as `schema apply` does.
13
- * So the flag went onto three invocations, two of which reject it, and both
14
- * `rebase db generate` and `rebase db migrate` exited 1 with `unknown flag:
15
- * --exclude` for every project declaring a `search` block.
16
- *
17
- * The domain is half the identity of an Atlas subcommand, and a guard that
18
- * looks only at `args` cannot tell `schema apply` from `migrate apply`.
19
- *
20
- * Nothing here touches a database or the filesystem, so the whole matrix is
21
- * unit-tested in `atlas-argv.test.ts`.
22
- */
23
-
24
- export interface AtlasInvocation {
25
- /** `schema` or `migrate`. */
26
- domain: string;
27
- /** The subcommand and its own flags, e.g. `["diff", "--dir", "file://…"]`. */
28
- args: string[];
29
- /** The target database, already rewritten for libpq. */
30
- url: string;
31
- /** The database Atlas plans against, already rewritten for libpq. */
32
- devUrl: string;
33
- /**
34
- * Everything the caller resolved for `--exclude`, in the order it should
35
- * appear. Dropped entirely when the subcommand does not accept the flag —
36
- * see {@link acceptsExcludeFlag}.
37
- */
38
- excludes?: string[];
39
- }
40
-
41
- /**
42
- * Does this Atlas invocation accept `--exclude`?
43
- *
44
- * Only `atlas schema apply` does, on the pinned 1.2.3. Measured against the
45
- * binary: `migrate diff` and `migrate apply` both answer `unknown flag:
46
- * --exclude`, and neither lists it in `--help` (`migrate apply` offers
47
- * `--url/--dir/--format/--revisions-schema/--dry-run/--lock-name/--lock-timeout/`
48
- * `--skip-lock/--baseline/--to-version/--tx-mode/--exec-order/--allow-dirty`).
49
- * Hence `domain === "schema"` and not just the subcommand.
50
- *
51
- * The `exclude` attribute of an `atlas.hcl` `env` block is not a way round it
52
- * either: measured, it is accepted and then ignored on the diff path, which is
53
- * the silent-no-match shape that costs the most time to notice.
54
- *
55
- * So the diff keeps the carved-out objects out of the migration afterwards
56
- * instead, with `stripCarvedOutStatements`.
57
- */
58
- export function acceptsExcludeFlag(domain: string, args: string[]): boolean {
59
- return domain === "schema" && args.includes("apply");
60
- }
61
-
62
- /** Assemble the full argv, connection flags and all. */
63
- export function buildAtlasArgs(invocation: AtlasInvocation): string[] {
64
- const { domain, args, url, devUrl, excludes = [] } = invocation;
65
- const argv = [domain, ...args];
66
-
67
- if (domain === "schema") {
68
- if (args.includes("apply")) {
69
- argv.push("--url", url, "--dev-url", devUrl);
70
- } else if (args.includes("clean") || args.includes("inspect")) {
71
- argv.push("--url", url);
72
- }
73
- } else if (domain === "migrate") {
74
- if (args.includes("diff")) {
75
- argv.push("--dev-url", devUrl);
76
- } else if (args.includes("apply") || args.includes("status")) {
77
- argv.push("--url", url, "--revisions-schema", "rebase");
78
- if (args.includes("apply")) {
79
- argv.push("--allow-dirty");
80
- }
81
- }
82
- }
83
-
84
- // Second line of defence, deliberately not a caller's responsibility: a
85
- // future caller that resolves excludes for the diff path gets them dropped
86
- // here rather than a command that will not run.
87
- if (acceptsExcludeFlag(domain, args)) {
88
- for (const exclude of excludes) {
89
- argv.push("--exclude", exclude);
90
- }
91
- }
92
-
93
- return argv;
94
- }