@rebasepro/server-postgres 0.0.1-canary.4829d6e

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 (121) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +86 -0
  3. package/dist/PostgresAdapter.d.ts +6 -0
  4. package/dist/PostgresBackendDriver.d.ts +150 -0
  5. package/dist/PostgresBootstrapper.d.ts +51 -0
  6. package/dist/auth/ensure-tables.d.ts +10 -0
  7. package/dist/auth/services.d.ts +250 -0
  8. package/dist/backup/backup-cli.d.ts +3 -0
  9. package/dist/backup/backup-cron.d.ts +53 -0
  10. package/dist/backup/backup-service.d.ts +85 -0
  11. package/dist/backup/index.d.ts +12 -0
  12. package/dist/backup/pg-tools.d.ts +110 -0
  13. package/dist/backup/retention.d.ts +35 -0
  14. package/dist/chunk-DSJWtz9O.js +40 -0
  15. package/dist/cli-errors.d.ts +42 -0
  16. package/dist/cli-helpers.d.ts +7 -0
  17. package/dist/cli.d.ts +1 -0
  18. package/dist/collections/PostgresCollectionRegistry.d.ts +47 -0
  19. package/dist/connection.d.ts +65 -0
  20. package/dist/data-transformer.d.ts +55 -0
  21. package/dist/databasePoolManager.d.ts +20 -0
  22. package/dist/history/HistoryService.d.ts +71 -0
  23. package/dist/history/ensure-history-table.d.ts +7 -0
  24. package/dist/index.d.ts +15 -0
  25. package/dist/index.es.js +23535 -0
  26. package/dist/index.es.js.map +1 -0
  27. package/dist/interfaces.d.ts +18 -0
  28. package/dist/schema/auth-bootstrap-sql.d.ts +24 -0
  29. package/dist/schema/auth-default-policies.d.ts +10 -0
  30. package/dist/schema/auth-schema.d.ts +2376 -0
  31. package/dist/schema/doctor-cli.d.ts +2 -0
  32. package/dist/schema/doctor.d.ts +58 -0
  33. package/dist/schema/dynamic-tables.d.ts +31 -0
  34. package/dist/schema/generate-drizzle-schema-logic.d.ts +2 -0
  35. package/dist/schema/generate-drizzle-schema.d.ts +1 -0
  36. package/dist/schema/generate-postgres-ddl-logic.d.ts +5 -0
  37. package/dist/schema/generate-postgres-ddl.d.ts +1 -0
  38. package/dist/schema/introspect-db-inference.d.ts +5 -0
  39. package/dist/schema/introspect-db-logic.d.ts +118 -0
  40. package/dist/schema/introspect-db.d.ts +1 -0
  41. package/dist/schema/introspect-runtime.d.ts +57 -0
  42. package/dist/schema/test-schema.d.ts +24 -0
  43. package/dist/security/policy-drift.d.ts +57 -0
  44. package/dist/security/rls-enforcement.d.ts +122 -0
  45. package/dist/services/BranchService.d.ts +47 -0
  46. package/dist/services/FetchService.d.ts +214 -0
  47. package/dist/services/PersistService.d.ts +39 -0
  48. package/dist/services/RelationService.d.ts +109 -0
  49. package/dist/services/cdc/CdcListener.d.ts +54 -0
  50. package/dist/services/cdc/trigger-cdc.d.ts +64 -0
  51. package/dist/services/collection-helpers.d.ts +38 -0
  52. package/dist/services/dataService.d.ts +110 -0
  53. package/dist/services/index.d.ts +4 -0
  54. package/dist/services/realtimeService.d.ts +298 -0
  55. package/dist/src-Eh-CZosp.js +595 -0
  56. package/dist/src-Eh-CZosp.js.map +1 -0
  57. package/dist/types.d.ts +3 -0
  58. package/dist/utils/drizzle-conditions.d.ts +138 -0
  59. package/dist/utils/pg-array-null-patch.d.ts +16 -0
  60. package/dist/utils/pg-error-utils.d.ts +65 -0
  61. package/dist/utils/table-classification.d.ts +8 -0
  62. package/dist/websocket.d.ts +18 -0
  63. package/package.json +113 -0
  64. package/src/PostgresAdapter.ts +58 -0
  65. package/src/PostgresBackendDriver.ts +1387 -0
  66. package/src/PostgresBootstrapper.ts +581 -0
  67. package/src/auth/ensure-tables.ts +367 -0
  68. package/src/auth/services.ts +1321 -0
  69. package/src/backup/backup-cli.ts +383 -0
  70. package/src/backup/backup-cron.ts +189 -0
  71. package/src/backup/backup-service.ts +299 -0
  72. package/src/backup/index.ts +12 -0
  73. package/src/backup/pg-tools.ts +231 -0
  74. package/src/backup/retention.ts +75 -0
  75. package/src/cli-errors.ts +265 -0
  76. package/src/cli-helpers.ts +196 -0
  77. package/src/cli.ts +786 -0
  78. package/src/collections/PostgresCollectionRegistry.ts +103 -0
  79. package/src/connection.ts +166 -0
  80. package/src/data-transformer.ts +733 -0
  81. package/src/databasePoolManager.ts +87 -0
  82. package/src/history/HistoryService.ts +272 -0
  83. package/src/history/ensure-history-table.ts +46 -0
  84. package/src/index.ts +15 -0
  85. package/src/interfaces.ts +60 -0
  86. package/src/schema/auth-bootstrap-sql.ts +41 -0
  87. package/src/schema/auth-default-policies.ts +125 -0
  88. package/src/schema/auth-schema.ts +233 -0
  89. package/src/schema/doctor-cli.ts +113 -0
  90. package/src/schema/doctor.ts +733 -0
  91. package/src/schema/dynamic-tables.test.ts +302 -0
  92. package/src/schema/dynamic-tables.ts +293 -0
  93. package/src/schema/generate-drizzle-schema-logic.ts +850 -0
  94. package/src/schema/generate-drizzle-schema.ts +131 -0
  95. package/src/schema/generate-postgres-ddl-logic.ts +490 -0
  96. package/src/schema/generate-postgres-ddl.ts +92 -0
  97. package/src/schema/introspect-db-inference.ts +238 -0
  98. package/src/schema/introspect-db-logic.ts +910 -0
  99. package/src/schema/introspect-db.ts +266 -0
  100. package/src/schema/introspect-runtime.test.ts +212 -0
  101. package/src/schema/introspect-runtime.ts +293 -0
  102. package/src/schema/test-schema.ts +11 -0
  103. package/src/security/policy-drift.test.ts +122 -0
  104. package/src/security/policy-drift.ts +159 -0
  105. package/src/security/rls-enforcement.ts +295 -0
  106. package/src/services/BranchService.ts +251 -0
  107. package/src/services/FetchService.ts +1661 -0
  108. package/src/services/PersistService.ts +329 -0
  109. package/src/services/RelationService.ts +1306 -0
  110. package/src/services/cdc/CdcListener.ts +167 -0
  111. package/src/services/cdc/trigger-cdc.ts +169 -0
  112. package/src/services/collection-helpers.ts +151 -0
  113. package/src/services/dataService.ts +246 -0
  114. package/src/services/index.ts +13 -0
  115. package/src/services/realtimeService.ts +1502 -0
  116. package/src/types.ts +4 -0
  117. package/src/utils/drizzle-conditions.ts +1162 -0
  118. package/src/utils/pg-array-null-patch.ts +42 -0
  119. package/src/utils/pg-error-utils.ts +227 -0
  120. package/src/utils/table-classification.ts +16 -0
  121. package/src/websocket.ts +640 -0
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,58 @@
1
+ import { CollectionConfig, Property } from "@rebasepro/types";
2
+ export type IssueSeverity = "error" | "warning" | "info";
3
+ export interface DoctorIssue {
4
+ severity: IssueSeverity;
5
+ category: "missing_table" | "missing_column" | "type_mismatch" | "missing_constraint" | "schema_stale" | "missing_enum" | "enum_value_mismatch" | "missing_foreign_key" | "sdk_stale";
6
+ table?: string;
7
+ column?: string;
8
+ expected?: string;
9
+ actual?: string;
10
+ message: string;
11
+ fix: string;
12
+ }
13
+ export interface DoctorReport {
14
+ collectionsToSchema: {
15
+ passed: boolean;
16
+ issues: DoctorIssue[];
17
+ };
18
+ collectionsToSdk: {
19
+ passed: boolean;
20
+ issues: DoctorIssue[];
21
+ };
22
+ schemaToDatabase: {
23
+ passed: boolean;
24
+ issues: DoctorIssue[];
25
+ };
26
+ summary: {
27
+ passed: number;
28
+ warnings: number;
29
+ errors: number;
30
+ };
31
+ }
32
+ export declare function getExpectedColumnType(prop: Property): string | null;
33
+ /**
34
+ * Re-exported so callers keep importing it from here, but the implementation is
35
+ * now shared with the runtime and the generators — a doctor that disagreed with
36
+ * the policy generator about which files are collections would compare the
37
+ * wrong thing.
38
+ */
39
+ export declare function loadCollections(collectionsPath: string): Promise<CollectionConfig[]>;
40
+ export declare function checkCollectionsVsSchema(collections: CollectionConfig[], schemaFilePath: string): Promise<{
41
+ passed: boolean;
42
+ issues: DoctorIssue[];
43
+ }>;
44
+ export declare function checkCollectionsVsSdk(collections: CollectionConfig[], sdkFilePath: string): Promise<{
45
+ passed: boolean;
46
+ issues: DoctorIssue[];
47
+ }>;
48
+ export declare function checkCollectionsVsDatabase(collections: CollectionConfig[], databaseUrl: string): Promise<{
49
+ passed: boolean;
50
+ issues: DoctorIssue[];
51
+ }>;
52
+ export declare function renderReport(report: DoctorReport): void;
53
+ export declare function runDoctor(options: {
54
+ collectionsPath: string;
55
+ schemaPath: string;
56
+ sdkPath: string;
57
+ databaseUrl?: string;
58
+ }): Promise<DoctorReport>;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Build drizzle tables at runtime from an introspected schema.
3
+ *
4
+ * CMS mode gets its drizzle tables from a generated `schema.generated.ts` that
5
+ * the developer commits. BaaS mode has no such file — it points at a database
6
+ * and serves it — so the equivalent table objects are constructed here from
7
+ * `information_schema` metadata.
8
+ *
9
+ * These are handed to drizzle as its schema, which keeps the relational query
10
+ * path (`db.query.*`) working; without them FetchService would fall back to
11
+ * plain selects and lose relation loading.
12
+ */
13
+ import { type PgTable } from "drizzle-orm/pg-core";
14
+ import { type Relations } from "drizzle-orm";
15
+ import type { TableMeta } from "./introspect-db-logic";
16
+ /**
17
+ * Build one drizzle table per introspected table, keyed by table name.
18
+ */
19
+ export declare function buildDrizzleTablesFromSchema(tablesMap: Map<string, TableMeta>, pgSchemaName?: string): Record<string, PgTable>;
20
+ /**
21
+ * Build drizzle `relations()` for the foreign keys, so the relational query
22
+ * path can actually load them.
23
+ *
24
+ * FetchService asks drizzle for `with: { <key>: true }`, keyed by the relation
25
+ * property on the collection. Tables alone don't satisfy that — without these,
26
+ * `?include=author` silently returns the raw `author_id` and no author.
27
+ *
28
+ * The keys here must match `buildRelations` in introspect-runtime.ts, which is
29
+ * what names the collection's relation properties.
30
+ */
31
+ export declare function buildDrizzleRelationsFromSchema(tablesMap: Map<string, TableMeta>, tables: Record<string, PgTable>): Record<string, Relations>;
@@ -0,0 +1,2 @@
1
+ import { CollectionConfig } from "@rebasepro/types";
2
+ export declare const generateSchema: (collections: CollectionConfig[], stripPolicies?: boolean) => Promise<string>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ import { CollectionConfig } from "@rebasepro/types";
2
+ export declare const generatePostgresDdl: (collections: CollectionConfig[], options?: {
3
+ includePolicies?: boolean;
4
+ }) => Promise<string>;
5
+ export declare const generatePostgresPoliciesDdl: (collections: CollectionConfig[]) => string;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ export interface InferenceResult {
2
+ propType?: string;
3
+ extra?: string;
4
+ }
5
+ export declare function inferPropertyFromData(columnName: string, pgDataType: string, currentPropType: string, sampleValues: unknown[], isPk: boolean): InferenceResult;
@@ -0,0 +1,118 @@
1
+ export interface TableRow {
2
+ table_name: string;
3
+ }
4
+ export interface TableColumn {
5
+ table_name: string;
6
+ column_name: string;
7
+ data_type: string;
8
+ udt_name: string;
9
+ is_nullable: string;
10
+ column_default: string | null;
11
+ atttypmod: number | null;
12
+ }
13
+ export interface EnumValue {
14
+ enum_name: string;
15
+ enum_value: string;
16
+ sort_order: number;
17
+ }
18
+ export interface PrimaryKeyRow {
19
+ table_name: string;
20
+ column_name: string;
21
+ }
22
+ export interface ForeignKeyRow {
23
+ table_name: string;
24
+ column_name: string;
25
+ foreign_table_name: string;
26
+ foreign_column_name: string;
27
+ }
28
+ export interface TableMeta {
29
+ name: string;
30
+ columns: TableColumn[];
31
+ pks: string[];
32
+ fks: ForeignKeyRow[];
33
+ }
34
+ export declare function singularize(word: string): string;
35
+ /**
36
+ * Convert a snake_case name to a human-readable Title Case label.
37
+ * e.g. "created_at" -> "Created At", "customer_id" -> "Customer Id"
38
+ */
39
+ export declare function humanize(snakeName: string): string;
40
+ /**
41
+ * Convert a snake_case table name to a camelCase + "Collection" variable name.
42
+ * e.g. "company_token" -> "companyTokenCollection"
43
+ */
44
+ export declare function toCollectionVarName(tableName: string): string;
45
+ export declare function getIconForTable(tableName: string): string;
46
+ /**
47
+ * Map a PostgreSQL data type to a Rebase property type.
48
+ */
49
+ export declare function mapPgType(dataType: string): string;
50
+ export declare function buildEnumMap(enumValues: EnumValue[]): Map<string, string[]>;
51
+ export declare function buildTablesMap(tables: TableRow[], columns: TableColumn[], pks: PrimaryKeyRow[], fks: ForeignKeyRow[]): Map<string, TableMeta>;
52
+ export declare function identifyJoinTables(tablesMap: Map<string, TableMeta>): Set<string>;
53
+ /**
54
+ * Property metadata used to compute display priority.
55
+ * Keeps computePropertyPriority free of any TableMeta coupling.
56
+ */
57
+ export interface PropertyOrderingContext {
58
+ /** The resolved Rebase property type (e.g. "string", "number", "date", "relation"). */
59
+ propType: string;
60
+ /** Whether this column is a primary key. */
61
+ isPk: boolean;
62
+ /** Whether this column is an enum (USER-DEFINED with matching values). */
63
+ isEnum: boolean;
64
+ /** Whether this is a storage/file-upload field (detected from column name). */
65
+ isStorage: boolean;
66
+ /** The PostgreSQL data_type (e.g. "text", "character varying", "jsonb"). */
67
+ pgDataType: string;
68
+ /** The original column index in PostgreSQL (for stable tiebreaking). */
69
+ originalIndex: number;
70
+ }
71
+ /**
72
+ * Compute a numeric priority score for a property.
73
+ * Lower scores appear first in the generated `propertiesOrder` array.
74
+ *
75
+ * The system uses 14 tiers (0–139), with the original column index
76
+ * added as a fractional tiebreaker (originalIndex / 10000) to
77
+ * guarantee stable ordering within the same tier.
78
+ *
79
+ * Pure function — no side effects.
80
+ */
81
+ export declare function computePropertyPriority(columnName: string, ctx: PropertyOrderingContext): number;
82
+ /**
83
+ * Sort a `propertiesOrder` array using the priority heuristic.
84
+ * Returns a new sorted array; does not mutate the input.
85
+ *
86
+ * @param entries - Array of { key, columnName, propType, ... } objects
87
+ * carrying the information needed to compute priority.
88
+ */
89
+ export interface PropertyOrderEntry {
90
+ /** The property key in the generated collection (may differ from columnName for relations). */
91
+ key: string;
92
+ /** The ordering context for this property. */
93
+ ctx: PropertyOrderingContext;
94
+ }
95
+ export declare function sortPropertiesOrder(entries: PropertyOrderEntry[]): string[];
96
+ export interface GeneratedFile {
97
+ tableName: string;
98
+ fileName: string;
99
+ content: string;
100
+ }
101
+ /**
102
+ * Generate the full TypeScript file content for a single collection.
103
+ * Pure function — no I/O.
104
+ */
105
+ export declare function generateCollectionFile(tableName: string, meta: TableMeta, allFks: ForeignKeyRow[], joinTables: Set<string>, tablesMap: Map<string, TableMeta>, enumMap: Map<string, string[]>, sampleData?: Record<string, unknown>[]): string;
106
+ /**
107
+ * Generate the content for an index.ts file that re-exports all collections.
108
+ */
109
+ export declare function generateIndexContent(fileNames: string[]): string;
110
+ /**
111
+ * Merge new exports into existing index.ts content.
112
+ * Returns the merged content string.
113
+ */
114
+ export declare function mergeIndexContent(existingContent: string, newFileNames: string[]): string;
115
+ /**
116
+ * Safely extract the host portion of a database URL for logging.
117
+ */
118
+ export declare function safeHostFromUrl(url: string): string;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Runtime introspection — builds collections in memory from the live database.
3
+ *
4
+ * This is what makes BaaS mode work with zero configuration: instead of loading
5
+ * collection files from disk, the server reads `information_schema` at boot and
6
+ * derives a collection per table, so any database is served over REST without a
7
+ * single config file.
8
+ *
9
+ * Distinct from `introspect-db.ts`, which runs the same queries but emits
10
+ * TypeScript *source* for a developer to edit and commit (CMS mode). The two
11
+ * share the mapping helpers in `introspect-db-logic.ts` so a table is described
12
+ * the same way whether it was generated or introspected.
13
+ */
14
+ import type { PostgresCollectionConfig } from "@rebasepro/types";
15
+ import { TableMeta } from "./introspect-db-logic";
16
+ export interface IntrospectedSchema {
17
+ tablesMap: Map<string, TableMeta>;
18
+ enumMap: Map<string, string[]>;
19
+ joinTables: Set<string>;
20
+ }
21
+ /** Whether a table carries an authorization model of its own. */
22
+ export interface TableRlsStatus {
23
+ table: string;
24
+ /** ALTER TABLE … ENABLE ROW LEVEL SECURITY has been run. */
25
+ rlsEnabled: boolean;
26
+ /** Policies attached to it. RLS enabled with none = nothing is visible. */
27
+ policyCount: number;
28
+ }
29
+ /**
30
+ * Read the RLS posture of each table in a schema.
31
+ *
32
+ * This is what decides whether baas mode may serve a table. A table with RLS
33
+ * disabled has no authorization model: since every authenticated request runs
34
+ * as `rebase_user`, and that role is granted DML on the schema, serving such a
35
+ * table hands every row to every logged-in user.
36
+ */
37
+ export declare function readRlsStatus(client: Queryable, pgSchema: string): Promise<Map<string, TableRlsStatus>>;
38
+ /** Minimal query surface — satisfied by pg.Client and pg.Pool alike. */
39
+ export interface Queryable {
40
+ query<R>(text: string, values?: unknown[]): Promise<{
41
+ rows: R[];
42
+ }>;
43
+ }
44
+ /**
45
+ * Read tables, columns, enums, primary keys and foreign keys for a schema.
46
+ * Mirrors the queries in introspect-db.ts.
47
+ */
48
+ export declare function introspectSchema(client: Queryable, pgSchema: string): Promise<IntrospectedSchema>;
49
+ /**
50
+ * Turn an introspected schema into collections.
51
+ *
52
+ * Join tables are skipped: they carry no identity of their own and exist to
53
+ * express a many-to-many edge between two other tables.
54
+ */
55
+ export declare function buildCollectionsFromSchema({ tablesMap, enumMap, joinTables }: IntrospectedSchema, pgSchema: string): PostgresCollectionConfig[];
56
+ /** Introspect the database and return ready-to-serve collections. */
57
+ export declare function introspectCollections(client: Queryable, pgSchema: string): Promise<PostgresCollectionConfig[]>;
@@ -0,0 +1,24 @@
1
+ export declare const testTable: import("drizzle-orm/pg-core").PgTableWithColumns<{
2
+ name: "test";
3
+ schema: undefined;
4
+ columns: {
5
+ id: import("drizzle-orm/pg-core").PgColumn<{
6
+ name: "id";
7
+ tableName: "test";
8
+ dataType: "string";
9
+ columnType: "PgText";
10
+ data: string;
11
+ driverParam: string;
12
+ notNull: true;
13
+ hasDefault: false;
14
+ isPrimaryKey: true;
15
+ isAutoincrement: false;
16
+ hasRuntimeDefault: false;
17
+ enumValues: [string, ...string[]];
18
+ baseColumn: never;
19
+ identity: undefined;
20
+ generated: undefined;
21
+ }, {}, {}>;
22
+ };
23
+ dialect: "pg";
24
+ }>;
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Compare the RLS policies a database actually has against the ones the
3
+ * collections describe.
4
+ *
5
+ * Policies live in Postgres; the collection config is only their *source*.
6
+ * Nothing reconciled the two, which is how the demo database served empty
7
+ * collections indefinitely: its policies granted `TO authenticated` (a Supabase
8
+ * role name) while requests run as `rebase_user`, so RLS filtered every row.
9
+ * The config was later corrected and the database never noticed — an empty
10
+ * table is indistinguishable from a table with no data.
11
+ *
12
+ * Expected policies are parsed from `generatePostgresPoliciesDdl`, the same
13
+ * function `db push` uses to write `drizzle/policies.sql`, so this compares
14
+ * against exactly what would be applied rather than a reimplementation.
15
+ */
16
+ import type { CollectionConfig } from "@rebasepro/types";
17
+ export interface PolicyRef {
18
+ schema: string;
19
+ table: string;
20
+ name: string;
21
+ /** Roles in the TO clause. */
22
+ roles: string[];
23
+ /** SELECT / INSERT / UPDATE / DELETE / ALL. */
24
+ command: string;
25
+ }
26
+ export interface PolicyDrift {
27
+ /** Described by the collections, absent from the database. */
28
+ missing: PolicyRef[];
29
+ /** In the database, described by no collection — stale pushes live here. */
30
+ orphaned: PolicyRef[];
31
+ /** Same policy name, different roles or command. */
32
+ diverged: {
33
+ expected: PolicyRef;
34
+ actual: PolicyRef;
35
+ differences: string[];
36
+ }[];
37
+ }
38
+ export interface Queryable {
39
+ query<R>(text: string, values?: unknown[]): Promise<{
40
+ rows: R[];
41
+ }>;
42
+ }
43
+ /** Parse the generated DDL rather than rebuilding the shape by hand. */
44
+ export declare function parseExpectedPolicies(ddl: string): PolicyRef[];
45
+ /**
46
+ * Diff expected against live.
47
+ *
48
+ * Compares names, roles and command only — all exact values. Policy
49
+ * *expressions* are deliberately not compared: Postgres rewrites `qual`/
50
+ * `with_check` when storing them (parenthesising, casting, schema-qualifying),
51
+ * so text comparison reports drift that does not exist, and a check that cries
52
+ * wolf gets ignored. Roles alone catch the failure this exists for.
53
+ */
54
+ export declare function checkPolicyDrift(client: Queryable, collections: CollectionConfig[]): Promise<PolicyDrift>;
55
+ export declare const hasDrift: (d: PolicyDrift) => boolean;
56
+ /** Human-readable report; empty string when the database matches the config. */
57
+ export declare function formatPolicyDrift(drift: PolicyDrift): string;
@@ -0,0 +1,122 @@
1
+ import { SQL } from "drizzle-orm";
2
+ /**
3
+ * Unified RLS enforcement — the "user context vs server context" model.
4
+ *
5
+ * Every operation runs in one of two contexts:
6
+ *
7
+ * - **User context** — a request authenticated (or anonymous) via
8
+ * `driver.withAuth(user)`. Runs as the restricted `rebase_user` role: a
9
+ * non-owner, NOSUPERUSER, NOBYPASSRLS role, so Postgres RLS binds *every*
10
+ * statement (SELECT, INSERT, UPDATE, DELETE). The collection's
11
+ * `securityRules` are the whole authorization model; app-layer callbacks
12
+ * are validation/side-effects, not a security boundary.
13
+ *
14
+ * - **Server context** — the base (owner) connection: auth flows, migrations,
15
+ * background jobs, and the explicit `rebase.dataAsAdmin` accessor. As table
16
+ * owner it bypasses RLS. This is the trusted plane, equivalent to
17
+ * Supabase's `service_role`.
18
+ *
19
+ * This module provides the three pieces:
20
+ *
21
+ * 1. {@link detectConnectionPosture} — is the connection subject to RLS at
22
+ * all? (superuser / BYPASSRLS / table owner ⇒ no)
23
+ * 2. {@link ensureAppRole} — idempotently provision `rebase_user` with
24
+ * SELECT/INSERT/UPDATE/DELETE grants (+ default privileges so future
25
+ * tables stay covered).
26
+ * 3. {@link applyAuthContext} — per-transaction: set the `app.*` GUCs the
27
+ * policies read (`auth.uid()` etc.) and `SET LOCAL ROLE rebase_user` so
28
+ * RLS binds. Transaction-scoped, so it composes with poolers.
29
+ *
30
+ * Provisioning runs from the framework's own bootstrap/migrate (which already
31
+ * self-creates the `auth` schema and functions) — enforcement is default-on,
32
+ * not an operator opt-in.
33
+ */
34
+ /** The restricted role every authenticated (user-context) request runs as. */
35
+ export declare const REBASE_USER_ROLE = "rebase_user";
36
+ /** Minimal SQL runner so callers can adapt drizzle or pg.Client. */
37
+ export type RawSqlRunner = (sqlText: string) => Promise<Record<string, unknown>[]>;
38
+ /** Minimal transaction surface needed by {@link applyAuthContext}. */
39
+ export interface SqlTx {
40
+ execute(query: SQL): Promise<unknown>;
41
+ }
42
+ export interface ConnectionPosture {
43
+ /** The connection's `current_user`. */
44
+ role: string;
45
+ superuser: boolean;
46
+ bypassRLS: boolean;
47
+ /** Owns at least one user table — owners bypass non-FORCE RLS. */
48
+ ownsTables: boolean;
49
+ /** True when RLS would NOT constrain this connection. */
50
+ privileged: boolean;
51
+ }
52
+ export interface AuthContext {
53
+ userId: string;
54
+ /** Raw roles as carried on the user (strings or `{ id }` objects). */
55
+ roles: unknown[];
56
+ }
57
+ export declare function detectConnectionPosture(run: RawSqlRunner): Promise<ConnectionPosture>;
58
+ /**
59
+ * Human-actionable instructions for when the connection cannot provision the
60
+ * user role itself (no CREATEROLE and role not pre-created by the platform).
61
+ */
62
+ export declare function appRoleSetupInstructions(connectionRole: string, schemas: string[]): string;
63
+ /**
64
+ * Idempotently provision the `rebase_user` role, membership for the current
65
+ * connection role, and DML grants (+ default privileges for future tables)
66
+ * on every existing schema in `schemas`.
67
+ *
68
+ * Split into privilege tiers so it works both when the connection is a
69
+ * superuser (creates everything) and when the platform pre-created the role
70
+ * and membership (e.g. CNPG `postInitApplicationSQL`) and the connection is
71
+ * merely the table owner — owners can always run the grant tier themselves.
72
+ *
73
+ * RLS still filters every row: these grants only make the tables *reachable*
74
+ * by the role; the policies decide which rows/commands actually pass.
75
+ *
76
+ * Throws with precise setup instructions when the role is missing and the
77
+ * connection cannot create it.
78
+ */
79
+ export declare function ensureAppRole(run: RawSqlRunner, schemas: string[]): Promise<void>;
80
+ /**
81
+ * Apply the authenticated context to a transaction: the `app.*` GUCs that RLS
82
+ * policies read via `auth.uid()` / `auth.roles()` / `auth.jwt()`, and — when
83
+ * `userRole` is set — `SET LOCAL ROLE` so RLS binds every statement in this
84
+ * transaction (reads *and* writes).
85
+ *
86
+ * GUCs are set with `is_local = true` and the role switch is `LOCAL`: both
87
+ * reset at commit/rollback, so pooled connections are never polluted.
88
+ *
89
+ * Fails closed by construction: if the role switch errors, the transaction
90
+ * aborts instead of proceeding privileged.
91
+ *
92
+ * SECURITY: this function is only ever called on the **user** path (the server
93
+ * context uses the base/owner driver and never calls it). The default policies
94
+ * treat `auth.uid() IS NULL` as the trusted server context, and `auth.uid()`
95
+ * is `NULLIF(current_setting('app.user_id'), '')` — so an EMPTY user id would
96
+ * be read as NULL and silently escalate a user request to server privileges.
97
+ * Coerce empty/blank ids to a sentinel here, at the single chokepoint, rather
98
+ * than trusting every caller (e.g. realtime subscription auth) to do it.
99
+ */
100
+ export declare function applyAuthContext(tx: SqlTx, auth: AuthContext, userRole?: string): Promise<void>;
101
+ /**
102
+ * Reject `pgRoles` that this server can never satisfy.
103
+ *
104
+ * `pgRoles` sets the `TO` clause of a generated policy, so a policy naming a
105
+ * role the request never runs as simply never applies — and RLS then filters
106
+ * every row. The table reads as empty, which is indistinguishable from having
107
+ * no data, so the mistake survives review and ships.
108
+ *
109
+ * Requests run as `rebase_user`, so a policy is only reachable if it targets
110
+ * `public` or a role `rebase_user` holds. Anything else is a configuration
111
+ * error worth failing the boot for.
112
+ */
113
+ export declare function validatePolicyPgRoles(run: RawSqlRunner, collections: {
114
+ slug?: string;
115
+ securityRules?: readonly {
116
+ name?: string;
117
+ pgRoles?: readonly string[];
118
+ }[];
119
+ }[],
120
+ /** The role requests actually run as: `rebase_user` when the connection is
121
+ * privileged enough to switch, otherwise the connection role itself. */
122
+ requestRole?: string): Promise<void>;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * BranchService
3
+ *
4
+ * Manages database branching by creating/deleting PostgreSQL databases
5
+ * using `CREATE DATABASE ... TEMPLATE`. Branch metadata is stored in the
6
+ * `rebase.branches` table in the default (main) database, following the
7
+ * same `rebase` schema convention used by entity_history, auth, etc.
8
+ */
9
+ import { BranchInfo } from "@rebasepro/types";
10
+ import { DrizzleClient } from "../interfaces";
11
+ import { DatabasePoolManager } from "../databasePoolManager";
12
+ export declare class BranchService {
13
+ private db;
14
+ private poolManager;
15
+ constructor(db: DrizzleClient, poolManager: DatabasePoolManager);
16
+ /**
17
+ * Ensure the `rebase.branches` metadata table exists in the default database.
18
+ * Idempotent — safe to call on every startup.
19
+ */
20
+ ensureBranchMetadataTable(): Promise<void>;
21
+ /**
22
+ * Create a new branch database by templating the source database.
23
+ *
24
+ * Uses `CREATE DATABASE ... TEMPLATE` for an instant, full-fidelity copy
25
+ * of both schema and data.
26
+ *
27
+ * @param name User-facing branch name (e.g., "feature_auth")
28
+ * @param options.source Source database to clone; defaults to the main database.
29
+ */
30
+ createBranch(name: string, options?: {
31
+ source?: string;
32
+ }): Promise<BranchInfo>;
33
+ /**
34
+ * Delete a branch database and remove its metadata.
35
+ * Cannot delete the main/default database.
36
+ */
37
+ deleteBranch(name: string): Promise<void>;
38
+ /**
39
+ * List all branches recorded in the metadata table.
40
+ * Optionally fetches database sizes from pg_database.
41
+ */
42
+ listBranches(): Promise<BranchInfo[]>;
43
+ /**
44
+ * Get info about a specific branch.
45
+ */
46
+ getBranchInfo(name: string): Promise<BranchInfo | undefined>;
47
+ }