import { CollectionConfig, Property, SecurityRule } from "@rebasepro/types"; export declare const resolveColumnName: (propName: string, prop?: Property | null) => string; export declare const getPrimaryKeyProp: (collection: CollectionConfig) => { name: string; type: "string" | "number"; isUuid: boolean; }; export declare const isNumericId: (collection: CollectionConfig) => boolean; export declare const getPrimaryKeyName: (collection: CollectionConfig) => string; export declare const isIdProperty: (propName: string, prop: Property, collection: CollectionConfig) => boolean; type ResolveCollection = (slug: string) => CollectionConfig | undefined; /** * The individual SQL statements a single security rule compiles to: a * `DROP POLICY IF EXISTS` / `CREATE POLICY` pair per operation, each a complete * statement (terminated by `;`, no trailing newline). * * This is the primitive the boot-time RLS applier runs one statement at a time * (the runtime's DB handle speaks the extended query protocol, which forbids * multiple commands in one execute), while `db push` writes the joined string. */ export declare const generatePolicyStatements: (collection: CollectionConfig, rule: SecurityRule, resolveCollection: ResolveCollection) => string[]; /** * Single-quote escaping for a SQL string literal (PostgreSQL doubles the * quote). Enum labels come straight from user-authored collection config, so a * label like `it's` closes the literal early and the whole generated file stops * parsing at the `CREATE TYPE`. Lives here rather than next to its other caller * because ensure-collection-tables already imports from this module — the * reverse would be a cycle. */ export declare const quoteSqlLiteral: (value: string) => string; export declare const getSqlColumnType: (propName: string, prop: Property, collection: CollectionConfig, collections: CollectionConfig[]) => string; /** * Everything a `search` block needs, as a file Rebase applies itself. * * Search is the one part of the schema Atlas does not own. Two independent * reasons, and either alone would be enough: * * 1. Its free tier refuses to *parse* a desired-state file that so much as * contains a function — "functions and procedures are available to * logged-in users only". A generated `tsvector` column cannot avoid one: * `unaccent` is STABLE and jsonb flattening needs a set-returning function, * so both have to be wrapped in an IMMUTABLE helper to be legal in a * generated column at all. * 2. Even with the file accepted, Atlas *wipes* the dev database it diffs * against, so a helper seeded there beforehand is gone by the time the plan * is analysed. There is no hook to reinstate it. * * So the column, its index and its helpers are excluded from Atlas's view * (`searchExcludePatterns`) and applied from here — the same arrangement the * RLS policies already use, and for the same underlying reason. * * Ordered as it must run: extensions, then helpers, then the column whose * expression calls them, then the index over that column. Every statement is * `IF NOT EXISTS` / `OR REPLACE`, because this is replayed on every push and * appended to migrations that run against databases at any stage of their * life. Empty when nothing opted in — the caller writes no file then. * * The leading half — extensions and helpers, without the table-shaped * statements — is available on its own as {@link searchPrerequisiteStatements}, * for the dev database Atlas analyses plans against. That database has none of * the project's tables, so it wants the functions and nothing else. */ export declare const searchPrerequisiteStatements: (allCollections: CollectionConfig[]) => string[]; export declare const generatePostgresSearchDdl: (allCollections: CollectionConfig[]) => string; /** * Glob patterns telling Atlas to leave the search column and its index alone. * * Without these, a desired state that omits search reads to Atlas as an * instruction to drop the column — taking the index and the whole search * feature with it on the next push. * * Fully qualified, `schema.table.object`, matching the include list. The * two-part form is what Atlas wants when the connection URL scopes it to one * schema and is *silently ignored* otherwise: it reads `posts.search_vector` * as a table named `search_vector` in a schema named `posts`, matches nothing, * and reports no error for the pattern that never fired. */ export declare const searchExcludePatterns: (allCollections: CollectionConfig[]) => string[]; export declare const generatePostgresDdl: (allCollections: CollectionConfig[], options?: { includePolicies?: boolean; includeSearch?: boolean; }) => Promise; /** The RLS statements one declared collection's table needs, ready to run. */ /** * A foreign key, as both its parts and the statement that creates it. * * `ALTER TABLE … ADD CONSTRAINT` has no `IF NOT EXISTS`, so a caller applying * these has to skip by name — hence the name is a field and not only a substring * of the SQL. */ export interface ForeignKeyPlan { constraintName: string; schema: string; /** Bare table name, no schema prefix. */ table: string; column: string; targetSchema: string; targetTable: string; targetColumn: string; sql: string; } /** A column a `relation` or `reference` property owns on its own table. */ export interface RelationalColumnPlan { schema: string; /** Bare table name, no schema prefix. */ table: string; column: string; /** Postgres type, exactly as the DDL generator declares it. */ type: string; /** Absent when the target collection is not part of this bundle. */ foreignKey?: ForeignKeyPlan; /** * What this column would have been called before `generateForeignKeyName` * learned to singularize — set only when the two differ and the column name * is the derived default rather than one the author wrote. * * Carried so the boot-time ensure can notice a database provisioned under * the old rule. It is never used to name anything. */ legacyColumn?: string; } /** The table behind a many-to-many `through` relation. */ export interface JunctionTablePlan { schema: string; /** Bare table name, no schema prefix. */ table: string; columns: { name: string; type: string; legacyName?: string; }[]; /** Both endpoint columns plus the composite primary key. */ createTable: string; foreignKeys: ForeignKeyPlan[]; } /** * The FK columns the declared collections own — one entry per `relation` * (`belongsTo` side) or `reference` property. * * Split out of {@link generatePostgresDdl} so the boot-time schema ensure can * create the same columns with the same names, types and constraints. Before * this it skipped them outright, which was survivable only because `db push` * always followed; on a managed tenant nothing follows, so a table arrived * without the column its own collection reads and wrote 400 on every insert. * * A relation whose target is not in the bundle yields no column at all (the * generator returns early on an unresolvable target); a `reference` whose target * is unknown yields the column without a constraint. Both mirror the generator * exactly — a divergence here is a schema fork between boot and `db push`. */ export declare const planRelationalColumns: (allCollections: CollectionConfig[]) => RelationalColumnPlan[]; /** * The junction tables a bundle's many-to-many relations imply. * * Derived from {@link resolveJunctionSpecs}, the same source the junction RLS * comes from, so a table created here always has policies planned for it — a * junction with row-level security left off is readable and writable by every * signed-in user, which is why the two must ship together. */ export declare const planJunctionTables: (allCollections: CollectionConfig[]) => JunctionTablePlan[]; export interface CollectionPolicyPlan { /** The table's schema (e.g. `public`, `rebase`). */ schema: string; /** The bare table name, no schema prefix. */ table: string; /** `schema.table` — matches the keys `readExistingSchema` returns. */ qualified: string; /** `ALTER TABLE … ENABLE ROW LEVEL SECURITY;` — locked by default. */ enableRls: string; /** `DROP POLICY IF EXISTS` / `CREATE POLICY` statements, in order. */ policyStatements: string[]; } /** * The per-table RLS plan for the *declared* collections, as executable * statements — what the managed runtime applies at boot so a freshly * provisioned tenant database serves data instead of 401ing every read. * * Mirrors {@link generatePostgresPoliciesDdl} exactly (same * `generatePolicyStatements`, same enable-RLS, same effective rules, same * derived junction rules), so boot and `db push` produce identical policies from * identical collections. * * Junction tables are included, and have to be: boot creates them now * ({@link planJunctionTables}), and a junction with RLS left off is readable and * writable by every signed-in user. A junction whose table is still absent is * skipped by the applier, not planned away here. */ export declare const planCollectionPolicies: (allCollections: CollectionConfig[]) => CollectionPolicyPlan[]; export declare const generatePostgresPoliciesDdl: (allCollections: CollectionConfig[]) => string; export {};