/** * PostgresBootstrapper * * Implements the `BackendBootstrapper` interface for PostgreSQL. */ import { NodePgDatabase } from "drizzle-orm/node-postgres"; import { BackendBootstrapper, CollectionConfig, type RealtimeChannelsConfig } from "@rebasepro/types"; import { PostgresBackendDriver } from "./PostgresBackendDriver"; import { RealtimeService } from "./services/realtimeService"; import { DatabasePoolManager } from "./databasePoolManager"; import { PostgresCollectionRegistry } from "./collections/PostgresCollectionRegistry"; import { type CdcTableRef } from "./services/cdc/trigger-cdc"; import { type UnknownFilterFieldsMode } from "./utils/drizzle-conditions"; export interface PostgresDriverConfig { connectionString?: string; adminConnectionString?: string; readConnectionString?: string; connection?: unknown; schema?: { tables?: Record; enums?: Record; relations?: Record; }; /** * PostgreSQL schema to read when deriving collections from the database * (BaaS mode). Defaults to `public`. */ introspectionSchema?: string; /** * Realtime options, both opt-in: * * - `channels` — retention. Without rules no channel keeps any history and * broadcast stays fire-and-forget. See {@link ChannelRetentionRule}. * - `bus` — the cross-instance transport for channel broadcast and * presence. Defaults to in-process only, which is correct for a single * instance and wrong for two. See {@link ChannelBusConfig}. */ realtime?: RealtimeChannelsConfig; /** * What to do with a filter field that resolves to no column at all. * Defaults to `"error"` — a filter that cannot be compiled would otherwise * be dropped, and a dropped condition can only widen the result set. * Set to `"warn"` to restore the pre-fix behaviour of dropping it silently. */ unknownFilterFields?: UnknownFilterFieldsMode; } /** * Opaque internals bag that PostgresBootstrapper stores during `initializeDriver()` * and re-uses in subsequent lifecycle hooks. */ export interface PostgresDriverInternals { db: NodePgDatabase; readDb?: NodePgDatabase; registry: PostgresCollectionRegistry; realtimeService: RealtimeService; driver: PostgresBackendDriver; poolManager?: DatabasePoolManager; /** * Attach CDC triggers to tables that did not exist when the driver * bootstrapped. Only set when database-level capture is actually active. * * Auth owns its own tables and creates them later in boot, so at driver * bootstrap they are legitimately missing and get skipped; without this * they would stay uninstrumented until the next restart. */ provisionCdcForTables?: (tables: CdcTableRef[]) => Promise; } /** * Which table name the boot-time drift check should look for, for one collection. * * A declared `table` IS the table name, not a hint to be second-guessed. This * used to ask the registry whether it had indexed the declared name and fall * back to the SLUG when it had not — but "the registry does not know this table" * is exactly the condition the drift check exists to report, so the fallback * fired precisely when it was most harmful. * * A collection with `slug: "usage-daily", table: "usage_daily"` was reported as * missing table `usage-daily`: a name that does not exist, should never exist, * and that nobody can find by looking. Worse, the remediation the caller prints * says to run `rebase db push` — which would then CREATE that invented table * beside the correct one, the same "second copy" hazard the misplaced-schema * branch further down exists to prevent. Seen in production, where a correctly * migrated database reported drift on every boot. * * The slug is used only when nothing was declared, which is the config shape * where the slug genuinely is the table name. * * Exported for its own test: the caller needs a live pool and a real database, * and this is the part that was wrong. */ export declare function resolveDriftCheckName(col: CollectionConfig, registeredTableNames: string[]): string; /** * Why the tables this backend serves are not in the database — the part of the * drift warning that has to be true rather than merely plausible. * * The three answers need three different actions, and only the caller knows * which one applies. This warning used to assert the first ("this runtime * applies the collection schema at boot unless REBASE_MIGRATE_ON_BOOT=none") * and then point at that variable and at driver-version skew. For an app whose * boot path contained no provisioning step at all, every word of that was a * dead end: nothing read the variable, and the driver was current. The advice * cost an investigation, which is a strictly worse outcome than saying less. * * Exported for its own test: the surrounding check needs a live pool and a real * database, and this is the part that was wrong. */ export declare function describeSchemaDriftCause(provisioning: { attempted: boolean; reason?: string; } | undefined): string[]; /** * Is this the local database `rebase init` scaffolds — i.e. the one case where * "you are connected as a superuser" is not news? * * The scaffold's own `docker-compose.yml` sets `POSTGRES_USER: rebase_app`, * which makes that role the cluster superuser, so the superuser advisory below * was the only WARN a brand-new project ever saw and it was about a decision * the tool had made for the developer. * * Of the two available fixes — provision a non-superuser table-owner role in * the scaffold, or recognise the local shape and stay quiet — this is the * second, because the first breaks the scaffold it is meant to improve: a * non-superuser owner cannot `CREATE EXTENSION` (search collections need * `pg_trgm`/`unaccent`, applied by `rebase db push` and again by the boot * schema-ensure), so the very first `pnpm run db:push` on a scaffolded project * with a search block would fail. Trading a working first run for a quieter log * line is the wrong trade. * * The condition is deliberately narrow — a *non-production* process talking to * a database on the loopback interface. A genuine production superuser * connection still warns, and so does a non-production process pointed at a * remote database (the usual "my dev machine writes to staging" mistake, where * the advisory is exactly right). NODE_ENV alone would not do: the scaffold * ships `NODE_ENV=development` and some deployments inherit it. */ export declare function isScaffoldedLocalDatabase(connectionString: string | undefined): boolean; export declare function createPostgresBootstrapper(pgConfig: PostgresDriverConfig): BackendBootstrapper;