import { mapPgType } from "./introspect-db-types"; import type { CheckFactsByTable } from "./introspect-db-constraints"; import type { TableClassification } from "./introspect-db-structure"; export interface TableRow { table_name: string; /** True for the parent of a partitioned table (`relkind = 'p'`). */ is_partitioned?: boolean; } export interface TableColumn { table_name: string; column_name: string; data_type: string; udt_name: string; is_nullable: string; column_default: string | null; atttypmod: number | null; /** 1-based position in the table, as declared. */ ordinal_position?: number; /** `"ALWAYS"` for a generated column, `"NEVER"` otherwise. */ is_generated?: string; /** `"YES"` for an identity column. */ is_identity?: string; /** `"ALWAYS"` or `"BY DEFAULT"` on an identity column. */ identity_generation?: string | null; /** The declared `varchar(n)` / `char(n)` bound, if any. */ character_maximum_length?: number | null; numeric_precision?: number | null; numeric_scale?: number | null; } export interface EnumValue { enum_name: string; enum_value: string; sort_order: number; } export interface PrimaryKeyRow { table_name: string; column_name: string; } export interface ForeignKeyRow { table_name: string; column_name: string; foreign_table_name: string; foreign_column_name: string; /** Name of the FK constraint — the only way to tell composite keys apart. */ constraint_name?: string; /** 1-based position of this column within its constraint. */ ordinal?: number; /** `"CASCADE"`, `"RESTRICT"`, `"SET NULL"`, `"SET DEFAULT"`, `"NO ACTION"`. */ delete_rule?: string; } /** A unique constraint or unique index, as an ordered column list. */ export interface UniqueConstraintRow { table_name: string; constraint_name: string; column_names: string[]; } /** A CHECK constraint, as `pg_get_constraintdef` renders it. */ export interface CheckConstraintRow { table_name: string; constraint_name: string; definition: string; } /** A `COMMENT ON TABLE` (null `column_name`) or `COMMENT ON COLUMN`. */ export interface CommentRow { table_name: string; column_name: string | null; comment: string; } /** * Everything one introspection run reads from the database. * * Passed around as one value so a new signal means a new field here rather than * a new parameter on every function between the query and the generator — the * shape `generateCollectionFile` had grown to seven positional arguments by. */ export interface SchemaMetadata { schema: string; tables: TableRow[]; columns: TableColumn[]; enumValues: EnumValue[]; pks: PrimaryKeyRow[]; fks: ForeignKeyRow[]; uniques: UniqueConstraintRow[]; checks: CheckConstraintRow[]; comments: CommentRow[]; /** * Row counts for the tables that needed one, capped — see `countRowsUpTo`. * Absent for every table introspection never had a reason to count. */ rowCounts: Record; } export interface TableMeta { name: string; columns: TableColumn[]; pks: string[]; fks: ForeignKeyRow[]; } export declare function singularize(word: string): string; /** * Convert a snake_case table name to a camelCase + "Collection" variable name. * e.g. "company_token" -> "companyTokenCollection" */ export declare function toCollectionVarName(tableName: string): string; export declare function getIconForTable(tableName: string): string; export { mapPgType }; export declare function buildEnumMap(enumValues: EnumValue[]): Map; export declare function buildTablesMap(tables: TableRow[], columns: TableColumn[], pks: PrimaryKeyRow[], fks: ForeignKeyRow[]): Map; /** * Join tables, identified by column name. * * Superseded for the CLI by `classifyTables` in `./introspect-db-structure`, * which asks the database instead: two single-column keys, unique together, no * payload column, nothing referencing the table. This rule folds away * `northwind.order_details` — which has the key shape and carries unit price, * quantity and discount — because it recognises `id`, `created_at` and * `updated_at` by name and calls everything else a foreign key. * * Still used by `./introspect-runtime`, which builds collections in memory from * a narrower set of catalog queries and has no unique-constraint or row-count * data to reason with. */ export declare function identifyJoinTables(tablesMap: Map): Set; /** * Property metadata used to compute display priority. * Keeps computePropertyPriority free of any TableMeta coupling. */ export interface PropertyOrderingContext { /** The resolved Rebase property type (e.g. "string", "number", "date", "relation"). */ propType: string; /** Whether this column is a primary key. */ isPk: boolean; /** Whether this column is an enum (USER-DEFINED with matching values). */ isEnum: boolean; /** Whether this is a storage/file-upload field (detected from column name). */ isStorage: boolean; /** The PostgreSQL data_type (e.g. "text", "character varying", "jsonb"). */ pgDataType: string; /** The original column index in PostgreSQL (for stable tiebreaking). */ originalIndex: number; } /** * Compute a numeric priority score for a property. * Lower scores appear first in the generated `propertiesOrder` array. * * The system uses 14 tiers (0–139), with the original column index * added as a fractional tiebreaker (originalIndex / 10000) to * guarantee stable ordering within the same tier. * * Pure function — no side effects. */ export declare function computePropertyPriority(columnName: string, ctx: PropertyOrderingContext): number; /** * Sort a `propertiesOrder` array using the priority heuristic. * Returns a new sorted array; does not mutate the input. * * @param entries - Array of { key, columnName, propType, ... } objects * carrying the information needed to compute priority. */ export interface PropertyOrderEntry { /** The property key in the generated collection (may differ from columnName for relations). */ key: string; /** The ordering context for this property. */ ctx: PropertyOrderingContext; } export declare function sortPropertiesOrder(entries: PropertyOrderEntry[]): string[]; export interface GeneratedFile { tableName: string; fileName: string; content: string; } /** * The structural analysis a run can hand the generator. * * Optional in full, and the generator degrades to exactly its previous output * without it. That is not politeness towards old callers: three existing test * suites and the `rebase init` scaffold path build a `TableMeta` by hand and * have no database to read constraints or row counts from, and they must keep * producing a valid collection. */ /** * Which `defineCollection` — if any — the project being generated into can import. * * A bare `const x: PostgresCollectionConfig = { … }` annotation widens `properties` * to `Record`, and every key-shaped field in the admin block — * `titleProperty`, `sort`, `propertiesOrder`, `listProperties`, `fixedFilter` — is * derived from those keys. Annotated, they accept any string: introspection was * emitting a `propertiesOrder` array that nothing checked, so renaming a column and * re-introspecting left a stale key that compiled silently. `defineCollection` is * the identity function whose `const P` type parameter keeps the keys literal, which * is what turns that checking on. * * There are two of them and they are not interchangeable: * * - `admin-types` — `@rebasepro/admin-types`. Its index side-effect-imports * `augment.ts`, so importing it is also what *declares* the `admin` block. Only a * project that depends on the package can resolve it. * - `common` — `@rebasepro/common`. Same key inference, no admin surface, no React * anywhere in its graph (`scripts/headless-guard` lists it as core). This is the * headless flavour. * - `annotation` — neither package is declared, so neither import would resolve and * the old annotation is the only honest thing to emit. Projects scaffolded before * `@rebasepro/common` joined the headless config package land here. * * The last two emit **no admin block, on the collection or on any property**. That is * not a downgrade: `@rebasepro/types` declares no `admin` field at all, so the block * introspection used to emit was a type error in every headless project it was * written into. See `packages/admin-types/src/augment.ts`. */ export type CollectionBuilder = "admin-types" | "common" | "annotation"; /** * The package specifiers the generated files name, spelled once. * * Written as constants rather than inline in the import templates below because * `scripts/headless-guard/check-types.mjs` scans core sources for `from * "@rebasepro/admin-types"` and cannot tell a real import from one this module * *writes*. It is right to be that blunt — the guard's whole value is that it * cannot be reasoned around — so the string simply never appears in that shape * here. Inlining them back into the templates re-breaks `check:types-headless`. */ export declare const ADMIN_TYPES_PACKAGE = "@rebasepro/admin-types"; export declare const COMMON_PACKAGE = "@rebasepro/common"; export declare const TYPES_PACKAGE = "@rebasepro/types"; export interface GenerationContext { metadata?: SchemaMetadata; classifications?: Map; checkFacts?: CheckFactsByTable; /** * Defaults to `admin-types`, which is what the generator has always emitted. * The CLI never relies on the default — `introspect-db.ts` detects the flavour * from the target project and passes it. See `detectCollectionBuilder`. */ builder?: CollectionBuilder; } /** * Generate the full TypeScript file content for a single collection. * Pure function — no I/O. */ export declare function generateCollectionFile(tableName: string, meta: TableMeta, allFks: ForeignKeyRow[], joinTables: Set, tablesMap: Map, enumMap: Map, sampleData?: Record[], context?: GenerationContext): string; /** * Generate the content for an index.ts file that re-exports all collections. */ export declare function generateIndexContent(fileNames: string[]): string; /** * Merge new exports into existing index.ts content. * Returns the merged content string. */ export declare function mergeIndexContent(existingContent: string, newFileNames: string[]): string; /** * Safely extract the host portion of a database URL for logging. */ export declare function safeHostFromUrl(url: string): string;