@rebasepro/server-postgres 0.19.1 → 0.19.2-canary.g09316f6
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.
- package/dist/{BranchService-ByLfM_Tt.js → BranchService-W3DMfcZZ.js} +2 -2
- package/dist/{BranchService-ByLfM_Tt.js.map → BranchService-W3DMfcZZ.js.map} +1 -1
- package/dist/PostgresBackendDriver.d.ts +81 -7
- package/dist/PostgresBootstrapper.d.ts +10 -0
- package/dist/{backup-cli-oM9WQVlV.js → backup-cli-DSpQyqcG.js} +2 -2
- package/dist/{backup-cli-oM9WQVlV.js.map → backup-cli-DSpQyqcG.js.map} +1 -1
- package/dist/{backup-service-Br3tqwsy.js → backup-service-DA7a6SUV.js} +2 -2
- package/dist/{backup-service-Br3tqwsy.js.map → backup-service-DA7a6SUV.js.map} +1 -1
- package/dist/{cli-errors-DxN4yzdH.js → cli-errors-C3g_kHBw.js} +137 -18
- package/dist/cli-errors-C3g_kHBw.js.map +1 -0
- package/dist/cli-errors.d.ts +19 -0
- package/dist/cli-helpers.d.ts +47 -0
- package/dist/cli.js +233 -19
- package/dist/cli.js.map +1 -1
- package/dist/{collection-index-BHgT_TKd.js → collection-index-DdnsxiJ_.js} +14 -4
- package/dist/{collection-index-BHgT_TKd.js.map → collection-index-DdnsxiJ_.js.map} +1 -1
- package/dist/collections/validate-relations.d.ts +7 -0
- package/dist/column-plan-helpers-CpILzHJS.js +145 -0
- package/dist/column-plan-helpers-CpILzHJS.js.map +1 -0
- package/dist/{doctor-D7HWmGzs.js → doctor-D5SrGJ5P.js} +175 -61
- package/dist/doctor-D5SrGJ5P.js.map +1 -0
- package/dist/{ensure-collection-policies-2c2NV8zX.js → ensure-collection-policies-CHO0moXQ.js} +5 -5
- package/dist/{ensure-collection-policies-2c2NV8zX.js.map → ensure-collection-policies-CHO0moXQ.js.map} +1 -1
- package/dist/{ensure-collection-tables-D4wJzM4y.js → ensure-collection-tables-Bkvehxdm.js} +318 -312
- package/dist/ensure-collection-tables-Bkvehxdm.js.map +1 -0
- package/dist/{ensure-tables-CIojK-Rx.js → ensure-tables-CYMtuuOd.js} +2 -2
- package/dist/{ensure-tables-CIojK-Rx.js.map → ensure-tables-CYMtuuOd.js.map} +1 -1
- package/dist/generate-drizzle-schema-logic-Dfyf_MRu.js +228 -0
- package/dist/generate-drizzle-schema-logic-Dfyf_MRu.js.map +1 -0
- package/dist/{generate-drizzle-schema-CwEgVyjO.js → generate-drizzle-schema-vSK-VdYT.js} +3 -3
- package/dist/generate-drizzle-schema-vSK-VdYT.js.map +1 -0
- package/dist/generate-postgres-ddl-logic-CR2xcS7e.js +475 -0
- package/dist/generate-postgres-ddl-logic-CR2xcS7e.js.map +1 -0
- package/dist/{generated-schema-staleness-DQdfJl2A.js → generated-schema-staleness-DRQe2BpC.js} +81 -3
- package/dist/{generated-schema-staleness-DQdfJl2A.js.map → generated-schema-staleness-DRQe2BpC.js.map} +1 -1
- package/dist/history/HistoryService.d.ts +23 -4
- package/dist/index.es.js +1991 -580
- package/dist/index.es.js.map +1 -1
- package/dist/introspect-db-logic-C6LQdTxj.js.map +1 -1
- package/dist/plan-schema-Hgl62S-w.js +1531 -0
- package/dist/plan-schema-Hgl62S-w.js.map +1 -0
- package/dist/{policy-drift-kNJ6X2Uj.js → policy-drift-B0GDRh7_.js} +2 -2
- package/dist/{policy-drift-kNJ6X2Uj.js.map → policy-drift-B0GDRh7_.js.map} +1 -1
- package/dist/{rls-bootstrap-sql-DLDnUjtx.js → rls-bootstrap-sql-BlzsOUtz.js} +111 -8
- package/dist/{rls-bootstrap-sql-DLDnUjtx.js.map → rls-bootstrap-sql-BlzsOUtz.js.map} +1 -1
- package/dist/{rls-enforcement-DeYEjYLt.js → rls-enforcement-BV12vBwQ.js} +2 -1
- package/dist/rls-enforcement-BV12vBwQ.js.map +1 -0
- package/dist/schema/catalogue-schema.d.ts +97 -0
- package/dist/schema/collection-index.d.ts +9 -0
- package/dist/schema/column-plan-helpers.d.ts +125 -0
- package/dist/schema/config-relations.d.ts +32 -0
- package/dist/schema/doctor-cli.js +4 -4
- package/dist/schema/doctor.d.ts +9 -0
- package/dist/schema/dynamic-tables.d.ts +27 -6
- package/dist/schema/ensure-collection-tables.d.ts +31 -268
- package/dist/schema/generate-drizzle-schema-logic.d.ts +26 -8
- package/dist/schema/generate-drizzle-schema.js +1 -1
- package/dist/schema/generate-postgres-ddl-logic.d.ts +84 -168
- package/dist/schema/generate-postgres-ddl.js +8 -2
- package/dist/schema/generate-postgres-ddl.js.map +1 -1
- package/dist/schema/generated-column-conflicts.d.ts +84 -0
- package/dist/schema/generated-schema-diff.d.ts +56 -0
- package/dist/schema/generated-schema-staleness.d.ts +33 -0
- package/dist/schema/introspect-db-logic.d.ts +2 -0
- package/dist/schema/plan/diff-plan.d.ts +220 -0
- package/dist/schema/plan/plan-schema.d.ts +84 -0
- package/dist/schema/plan/render-ddl.d.ts +62 -0
- package/dist/schema/plan/render-drizzle.d.ts +31 -0
- package/dist/schema/plan/types.d.ts +410 -0
- package/dist/schema/plan/updated-at-trigger.d.ts +22 -0
- package/dist/schema/relation-names.d.ts +28 -0
- package/dist/schema/search-column.d.ts +9 -0
- package/dist/security/rls-enforcement.d.ts +17 -0
- package/dist/services/FetchService.d.ts +176 -36
- package/dist/services/PersistService.d.ts +17 -4
- package/dist/services/RelationService.d.ts +11 -3
- package/dist/services/collection-helpers.d.ts +16 -2
- package/dist/services/dataService.d.ts +29 -2
- package/dist/services/field-op-sql.d.ts +16 -0
- package/dist/services/field-viewer.d.ts +19 -0
- package/dist/services/realtimeService.d.ts +35 -1
- package/dist/services/row-pipeline.d.ts +29 -5
- package/dist/services/soft-delete.d.ts +73 -0
- package/dist/utils/pg-error-utils.d.ts +41 -1
- package/dist/{vector-index-DkWxm2de.js → vector-index-CMJKb3C9.js} +3 -14
- package/dist/{vector-index-DkWxm2de.js.map → vector-index-CMJKb3C9.js.map} +1 -1
- package/package.json +18 -10
- package/dist/cli-errors-DxN4yzdH.js.map +0 -1
- package/dist/doctor-D7HWmGzs.js.map +0 -1
- package/dist/ensure-collection-tables-D4wJzM4y.js.map +0 -1
- package/dist/generate-drizzle-schema-CwEgVyjO.js.map +0 -1
- package/dist/generate-drizzle-schema-logic-so2Z_lyC.js +0 -528
- package/dist/generate-drizzle-schema-logic-so2Z_lyC.js.map +0 -1
- package/dist/generate-postgres-ddl-logic-CtnpIBVv.js +0 -1312
- package/dist/generate-postgres-ddl-logic-CtnpIBVv.js.map +0 -1
- package/dist/rls-enforcement-DeYEjYLt.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"generate-postgres-ddl-logic-CtnpIBVv.js","names":[],"sources":["../src/schema/search-column.ts","../src/schema/generate-postgres-ddl-logic.ts"],"sourcesContent":["/**\n * The one place a collection's `search` block becomes SQL.\n *\n * Four things describe a Postgres table in this codebase — the DDL generator,\n * the Drizzle schema generator, the runtime table builder for BaaS mode, and\n * the boot-time schema ensure — and each of them has, at some point, described\n * a column differently from the others. The `varchar(255)` note in\n * `generate-postgres-ddl-logic` is one such scar: the same property produced a\n * capped column down one path and an uncapped one down the other, and nothing\n * failed until a user hit the cap.\n *\n * So the search column is not implemented four times. It is computed once,\n * here, and every generator renders the same {@link SearchColumnSpec}. There is\n * a test asserting exactly that (`search-column-contract.test.ts`); the point of\n * this module is that the test has something to assert *about*.\n *\n * ## Why the expressions look the way they do\n *\n * A `GENERATED ALWAYS AS … STORED` expression must be strictly IMMUTABLE, and\n * Postgres is stricter here than intuition. Verified against PostgreSQL 18:\n *\n * | expression | immutable |\n * |-----------------------------------------|-----------|\n * | `to_tsvector('spanish', col)` | yes |\n * | `to_tsvector(col)` (1-arg) | **no** — depends on `default_text_search_config` |\n * | `array_to_string(col, ' ')` | **no** |\n * | `col::text` on `text[]` | **no** |\n * | `to_jsonb(col)` | **no** |\n * | `unaccent(col)` | **no** — dictionary lookup is STABLE |\n * | `jsonb_to_tsvector('spanish', j, '[\"string\"]')` | yes |\n * | `setweight(...) || setweight(...)` | yes |\n *\n * Three of the four things a real search column needs are therefore unavailable\n * directly, which is why {@link searchHelperFunctions} exists: each wraps a\n * stable built-in in an SQL function declared IMMUTABLE. That declaration is a\n * promise, and it is a true one for these three — array joining, JSON string\n * extraction and accent folding are all deterministic for a given input; the\n * built-ins are marked stable only because they must account for element types\n * and dictionaries in general.\n *\n * The alternative was to skip `unaccent` and text arrays entirely. That is not\n * a real option in an accented language: Postgres stems `auditoría` to\n * `auditor` and `auditoria` to `auditori` — *different lexemes* — so a query\n * typed without accents misses every row that carries them.\n */\nimport {\n CollectionConfig,\n Property,\n StringProperty,\n ArrayProperty,\n MapProperty,\n SearchConfig,\n SearchField,\n SearchWeight,\n isPostgresCollectionConfig,\n DEFAULT_SEARCH_COLUMN,\n DEFAULT_SEARCH_LANGUAGE,\n DEFAULT_SEARCH_WEIGHT,\n DEFAULT_FUZZY_THRESHOLD\n} from \"@rebasepro/types\";\nimport { createHash } from \"node:crypto\";\nimport { getTableName } from \"@rebasepro/common\";\nimport { toSnakeCase, toPostgresIdentifier } from \"@rebasepro/utils\";\n\n/** Schema-qualified so a collection outside `public` still resolves them. */\nconst HELPER_SCHEMA = \"public\";\n\n/**\n * Names of the helper functions. Frozen: they are recorded in the stored\n * generation expression of every search column ever created, so renaming one\n * orphans every table that already has a search column.\n */\nexport const SEARCH_TEXT_FN = `${HELPER_SCHEMA}.rebase_search_text`;\nexport const SEARCH_UNACCENT_FN = `${HELPER_SCHEMA}.rebase_search_unaccent`;\n\n/** How a declared path reaches text, which decides the SQL that extracts it. */\ntype FieldKind = \"text\" | \"text_array\" | \"jsonb\";\n\n/** One resolved field: where it lives, how to read it, what it is worth. */\nexport interface ResolvedSearchField {\n /** The path exactly as the author wrote it, for error messages. */\n path: string;\n /** The physical column the path starts at. */\n column: string;\n /** Dotted remainder addressed inside a JSONB column, if any. */\n jsonPath: string[];\n kind: FieldKind;\n weight: SearchWeight;\n /** The `setweight(to_tsvector(…), 'X')` term this field contributes. */\n sql: string;\n /** The plain-text term this field contributes, for the fuzzy column. */\n textSql: string;\n}\n\n/** Everything the generators need to render one collection's search column. */\nexport interface SearchColumnSpec {\n schema: string;\n table: string;\n /** The generated `tsvector` column. */\n column: string;\n language: string;\n unaccent: boolean;\n fields: ResolvedSearchField[];\n /** Body of `GENERATED ALWAYS AS ( … ) STORED` for the tsvector column. */\n expression: string;\n indexName: string;\n /** Extensions that must exist before the column can be created. */\n extensions: string[];\n fuzzy?: {\n column: string;\n expression: string;\n indexName: string;\n threshold: number;\n };\n}\n\n/** Raised when a `search` block names something that cannot be searched. */\nexport class SearchConfigError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"SearchConfigError\";\n }\n}\n\n/** The `search` block of a collection, or undefined when it has none. */\nexport const getSearchConfig = (collection: CollectionConfig): SearchConfig | undefined =>\n isPostgresCollectionConfig(collection) ? collection.search : undefined;\n\n/**\n * Refuse a `search` block on a collection this engine does not store.\n *\n * The type only permits one on a `PostgresCollectionConfig`, so TypeScript\n * already stops the ordinary case. This catches the rest — a JS config, a cast,\n * a collection whose `engine` was changed after the block was written — because\n * the alternative is the exact failure the block exists to prevent: a developer\n * who declared what to index, saw no error, and got the substring fallback.\n *\n * Called with *every* collection, before the Postgres ones are filtered out.\n */\nexport const assertSearchIsPostgresOnly = (collections: CollectionConfig[]): void => {\n for (const collection of collections) {\n if (isPostgresCollectionConfig(collection)) continue;\n if (!(collection as { search?: unknown }).search) continue;\n const engine = (collection as { engine?: string }).engine ?? \"non-postgres\";\n throw new SearchConfigError(\n `${collection.slug}.search: full-text search is a Postgres feature, and this collection is served by \\`${engine}\\`. ` +\n \"Remove the block — it would otherwise look configured while `.search()` kept using the default substring match.\"\n );\n }\n};\n\nconst columnNameOf = (propName: string, prop?: Property | null): string =>\n prop && \"columnName\" in prop && typeof prop.columnName === \"string\" ? prop.columnName : toSnakeCase(propName);\n\n/**\n * Classify a property for search purposes.\n *\n * Deliberately narrower than `getSqlColumnType`: search only cares whether a\n * value reaches text, and the mapping from property to *physical* type is\n * asserted against `getSqlColumnType` in the contract test rather than\n * duplicated here.\n *\n * Returns null for anything that is not text-bearing, which the caller turns\n * into a boot error naming the property.\n */\nconst classify = (prop: Property): { kind: FieldKind; reason?: string } | null => {\n switch (prop.type) {\n case \"string\": {\n const sp = prop as StringProperty;\n if (sp.enum) {\n return { kind: \"text\", reason: \"enum\" };\n }\n if (sp.isId === \"uuid\" || sp.columnType === \"uuid\") {\n return { kind: \"text\", reason: \"uuid\" };\n }\n return { kind: \"text\" };\n }\n case \"map\": {\n const mp = prop as MapProperty;\n // A `json` column is not `jsonb`, and the cast between them is not\n // immutable. Declaring `columnType: \"json\"` puts the value out of\n // reach of a generated column.\n if (mp.columnType === \"json\") return { kind: \"jsonb\", reason: \"json\" };\n return { kind: \"jsonb\" };\n }\n case \"array\": {\n const ap = prop as ArrayProperty;\n let colType = ap.columnType;\n if (!colType && ap.of && !Array.isArray(ap.of)) {\n const of = ap.of as Property;\n if (of.type === \"string\") colType = \"text[]\";\n else if (of.type === \"number\") colType = of.validation?.integer ? \"integer[]\" : \"numeric[]\";\n else if (of.type === \"boolean\") colType = \"boolean[]\";\n }\n if (colType === \"text[]\") return { kind: \"text_array\" };\n if (colType === \"json\") return { kind: \"jsonb\", reason: \"json\" };\n if (colType === \"integer[]\" || colType === \"boolean[]\" || colType === \"numeric[]\") {\n return { kind: \"text_array\", reason: \"non_text_array\" };\n }\n // Everything else lands in JSONB, which the JSON extractor handles.\n return { kind: \"jsonb\" };\n }\n default:\n return null;\n }\n};\n\nconst normalize = (inner: string, unaccent: boolean): string =>\n unaccent ? `${SEARCH_UNACCENT_FN}(${inner})` : inner;\n\n/** SQL reading one field as plain text, before normalization. */\nconst rawTextSql = (field: { column: string; jsonPath: string[]; kind: FieldKind }): string => {\n const col = `\"${field.column}\"`;\n if (field.kind === \"text\") return `coalesce(${col}, '')`;\n if (field.kind === \"text_array\") return `${SEARCH_TEXT_FN}(coalesce(${col}, '{}'::text[]))`;\n // JSONB, optionally addressed at a path inside the document.\n const target = field.jsonPath.length === 0\n ? col\n : field.jsonPath.length === 1\n ? `${col} -> ${quote(field.jsonPath[0])}`\n : `${col} #> ${quote(`{${field.jsonPath.join(\",\")}}`)}`;\n return `${SEARCH_TEXT_FN}(coalesce(${target}, '{}'::jsonb))`;\n};\n\nconst quote = (v: string): string => `'${v.replace(/'/g, \"''\")}'`;\n\n/**\n * Resolve and validate one declared field path.\n *\n * A path that does not resolve throws. The whole point of an explicit block is\n * that the author knows what is indexed; a silently dropped field would make it\n * a guess again, and the failure — a search that returns nothing for content\n * that is plainly in the row — is invisible from the outside.\n */\nconst resolveField = (\n entry: string | SearchField,\n collection: CollectionConfig,\n cfg: SearchConfig\n): ResolvedSearchField => {\n const path = typeof entry === \"string\" ? entry : entry.path;\n const weight = (typeof entry === \"string\" ? undefined : entry.weight) ?? DEFAULT_SEARCH_WEIGHT;\n const where = `${collection.slug}.search`;\n\n if (!path || typeof path !== \"string\") {\n throw new SearchConfigError(`${where}: every entry in \\`fields\\` needs a property path.`);\n }\n\n const [head, ...rest] = path.split(\".\");\n const prop = collection.properties?.[head] as Property | undefined;\n if (!prop) {\n const known = Object.keys(collection.properties ?? {}).join(\", \");\n throw new SearchConfigError(\n `${where}: \"${path}\" starts at property \"${head}\", which this collection does not declare. Known properties: ${known}.`\n );\n }\n\n const classified = classify(prop);\n if (!classified) {\n throw new SearchConfigError(\n `${where}: \"${path}\" is a \\`${prop.type}\\` property, which holds no text to search. ` +\n `Searchable kinds are \\`string\\`, \\`string[]\\` and \\`map\\` (or a path inside one).`\n );\n }\n if (classified.reason === \"enum\") {\n throw new SearchConfigError(\n `${where}: \"${path}\" is an enum. Enums are a fixed vocabulary — filter on them with \\`where\\` instead, which is exact and uses an index.`\n );\n }\n if (classified.reason === \"uuid\") {\n throw new SearchConfigError(\n `${where}: \"${path}\" is a UUID column. Look it up by id rather than searching it.`\n );\n }\n if (classified.reason === \"json\") {\n throw new SearchConfigError(\n `${where}: \"${path}\" is a \\`json\\` column, and the cast from \\`json\\` to \\`jsonb\\` is not immutable, so it cannot feed a generated column. Declare the property as \\`jsonb\\` (the default) to search it.`\n );\n }\n if (classified.reason === \"non_text_array\") {\n throw new SearchConfigError(\n `${where}: \"${path}\" is an array of numbers or booleans. Only \\`string[]\\` carries text to search.`\n );\n }\n\n if (rest.length > 0 && classified.kind !== \"jsonb\") {\n throw new SearchConfigError(\n `${where}: \"${path}\" addresses a path inside \"${head}\", but \"${head}\" is a \\`${prop.type}\\` property, not a \\`map\\`. Only map properties have paths inside them.`\n );\n }\n\n const column = columnNameOf(head, prop);\n const field = { column, jsonPath: rest, kind: classified.kind };\n const textSql = normalize(rawTextSql(field), cfg.unaccent === true);\n const language = cfg.language ?? DEFAULT_SEARCH_LANGUAGE;\n\n return {\n path,\n column,\n jsonPath: rest,\n kind: classified.kind,\n weight,\n sql: `setweight(to_tsvector(${quote(language)}, ${textSql}), ${quote(weight)})`,\n textSql\n };\n};\n\n/**\n * Build the full spec for a collection, or undefined when it has not opted in.\n *\n * Throws {@link SearchConfigError} on a config that cannot be honoured. Callers\n * at boot surface that as a startup failure — a search block that half-works is\n * worse than one that refuses.\n */\nexport const buildSearchColumnSpec = (collection: CollectionConfig): SearchColumnSpec | undefined => {\n const cfg = getSearchConfig(collection);\n if (!cfg) return undefined;\n\n if (!Array.isArray(cfg.fields) || cfg.fields.length === 0) {\n throw new SearchConfigError(\n `${collection.slug}.search: \\`fields\\` is empty. Name the properties to index, or remove the \\`search\\` block to keep the default ILIKE behaviour.`\n );\n }\n\n const table = getTableName(collection);\n const schema = isPostgresCollectionConfig(collection) && collection.schema ? collection.schema : \"public\";\n const column = cfg.column ?? DEFAULT_SEARCH_COLUMN;\n\n if (collection.properties?.[column]) {\n throw new SearchConfigError(\n `${collection.slug}.search: the generated column \"${column}\" collides with a declared property of the same name. Set \\`search.column\\` to something else.`\n );\n }\n\n const fields = cfg.fields.map(entry => resolveField(entry, collection, cfg));\n\n const seen = new Set<string>();\n for (const f of fields) {\n if (seen.has(f.path)) {\n throw new SearchConfigError(`${collection.slug}.search: \"${f.path}\" is listed twice.`);\n }\n seen.add(f.path);\n }\n\n const extensions: string[] = [];\n if (cfg.unaccent) extensions.push(\"unaccent\");\n if (cfg.fuzzy) extensions.push(\"pg_trgm\");\n\n const spec: SearchColumnSpec = {\n schema,\n table,\n column,\n language: cfg.language ?? DEFAULT_SEARCH_LANGUAGE,\n unaccent: cfg.unaccent === true,\n fields,\n expression: fields.map(f => f.sql).join(\" || \"),\n indexName: toPostgresIdentifier(`${table}_${column}_gin`),\n extensions\n };\n\n if (cfg.fuzzy) {\n const fuzzyColumn = `${column}_text`;\n if (collection.properties?.[fuzzyColumn]) {\n throw new SearchConfigError(\n `${collection.slug}.search: \\`fuzzy\\` needs the column \"${fuzzyColumn}\", which collides with a declared property. Set \\`search.column\\` to something else.`\n );\n }\n spec.fuzzy = {\n column: fuzzyColumn,\n // Concatenated with spaces so a trigram never spans two fields.\n expression: fields.map(f => f.textSql).join(\" || ' ' || \"),\n indexName: toPostgresIdentifier(`${table}_${fuzzyColumn}_trgm`),\n threshold: cfg.fuzzyThreshold ?? DEFAULT_FUZZY_THRESHOLD\n };\n }\n\n return spec;\n};\n\n/**\n * The IMMUTABLE wrappers the generated expressions call.\n *\n * `CREATE OR REPLACE` so a boot against an existing database is a no-op rather\n * than an error, and idempotent for the same reason every other boot-time DDL\n * statement here is.\n *\n * The bodies are stable built-ins wrapped in an immutable promise — see the\n * module comment for why that promise is sound. `STRICT` matters: it makes NULL\n * in mean NULL out without executing the body, which is what the `coalesce` at\n * each call site then absorbs.\n */\nexport const searchHelperFunctions = (spec: SearchColumnSpec): string[] => {\n const statements: string[] = [\n // text[] → \" \"-joined text.\n `CREATE OR REPLACE FUNCTION ${SEARCH_TEXT_FN}(text[]) RETURNS text\\n` +\n ` LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS\\n` +\n ` $$ SELECT array_to_string($1, ' ') $$;`,\n // jsonb → every string value at or below the node, space-joined. Keys\n // are not values: indexing them would make `certifications` itself a\n // search term on every row that has the field at all.\n `CREATE OR REPLACE FUNCTION ${SEARCH_TEXT_FN}(jsonb) RETURNS text\\n` +\n ` LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS\\n` +\n ` $$ SELECT coalesce(string_agg(v, ' '), '')\\n` +\n ` FROM jsonb_array_elements_text(jsonb_path_query_array($1, 'strict $.**?(@.type() == \"string\")')) AS v $$;`\n ];\n if (spec.unaccent) {\n // The two-argument form with an explicit dictionary is the one that can\n // honestly be called immutable: the single-argument form resolves the\n // dictionary through the current search_path at call time.\n statements.push(\n `CREATE OR REPLACE FUNCTION ${SEARCH_UNACCENT_FN}(text) RETURNS text\\n` +\n ` LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS\\n` +\n ` $$ SELECT ${HELPER_SCHEMA}.unaccent('${HELPER_SCHEMA}.unaccent'::regdictionary, $1) $$;`\n );\n }\n return statements;\n};\n\n/**\n * `CREATE EXTENSION` statements the spec's expressions depend on.\n *\n * `WITH SCHEMA public` is load-bearing, not tidiness. An unqualified\n * `CREATE EXTENSION` installs into the first schema on `search_path`, which\n * defaults to `\"$user\", public` — and the scaffold's database role is named\n * `rebase`, the same as the schema the generator creates one statement earlier.\n * So the moment that schema exists, `CREATE EXTENSION unaccent` puts the\n * dictionary in `rebase`, and every reference to `public.unaccent` below fails\n * with \"text search dictionary does not exist\". Observed, not theorised.\n */\nexport const searchExtensionStatements = (spec: SearchColumnSpec): string[] =>\n spec.extensions.map(e => `CREATE EXTENSION IF NOT EXISTS ${e} WITH SCHEMA ${HELPER_SCHEMA};`);\n\n/** The column definition as it appears inside `CREATE TABLE`. */\nexport const searchColumnDefinition = (spec: SearchColumnSpec): string =>\n `\"${spec.column}\" tsvector GENERATED ALWAYS AS (${spec.expression}) STORED`;\n\n/** The fuzzy column definition, when the spec asks for one. */\nexport const fuzzyColumnDefinition = (spec: SearchColumnSpec): string | undefined =>\n spec.fuzzy ? `\"${spec.fuzzy.column}\" text GENERATED ALWAYS AS (${spec.fuzzy.expression}) STORED` : undefined;\n\n/**\n * Index statements for the spec.\n *\n * `CONCURRENTLY` is deliberately *not* used here. This form is emitted into a\n * SQL file replayed as one unit — a migration, or `search.sql` — where a\n * concurrent build is not allowed. The boot-time ensure path runs statement by\n * statement against tables that are live and populated, and uses the\n * concurrent form instead; see `ensureSearchColumns`.\n */\nexport const searchIndexStatements = (spec: SearchColumnSpec): string[] => {\n const statements = [\n `CREATE INDEX IF NOT EXISTS \"${spec.indexName}\" ON \"${spec.schema}\".\"${spec.table}\" USING GIN (\"${spec.column}\");`\n ];\n if (spec.fuzzy) {\n statements.push(\n // The operator class is resolved through `search_path` like any\n // other object, so it is qualified for the same reason the\n // extension is installed explicitly.\n `CREATE INDEX IF NOT EXISTS \"${spec.fuzzy.indexName}\" ON \"${spec.schema}\".\"${spec.table}\" USING GIN (\"${spec.fuzzy.column}\" ${HELPER_SCHEMA}.gin_trgm_ops);`\n );\n }\n return statements;\n};\n\n// ── Telling a changed `search` block from an unchanged one ──────────────────\n\n/**\n * Marker on the comment of every generated search column this module creates.\n *\n * Versioned because the fingerprint below is only comparable against itself: a\n * future change to how it is computed has to read as \"not stamped by this\n * version\" rather than as drift on every existing column.\n */\nexport const SEARCH_STAMP_PREFIX = \"rebase:search:v1:\";\n\n/**\n * A stable fingerprint of one generated column's expression.\n *\n * Why a stamp rather than reading the expression back: Postgres stores a\n * generated column's expression *parsed*, and hands it back deparsed — casts\n * made explicit, identifiers requoted, schema qualifications added or dropped\n * according to `search_path`. Comparing that text to the text we generated\n * would report drift on wording, and this comparison decides whether a boot\n * refuses, so a false positive is an outage. The stamp is written by the same\n * code that writes the column, so equality means what it says.\n */\nexport const searchExpressionFingerprint = (expression: string): string =>\n `${SEARCH_STAMP_PREFIX}${createHash(\"sha256\").update(expression).digest(\"hex\").slice(0, 16)}`;\n\n/** One generated column, with the fingerprint that identifies its expression. */\nexport interface SearchColumnStamp {\n column: string;\n /** The expression the column is generated from. */\n expression: string;\n fingerprint: string;\n /** `COMMENT ON COLUMN …`, which is where the fingerprint is recorded. */\n sql: string;\n}\n\n/**\n * The stamps for a spec's generated columns — one per column, never shared.\n *\n * Per column on purpose: turning `fuzzy` on adds a second column and changes\n * nothing about the first, and a spec-wide fingerprint would report the\n * untouched `tsvector` column as drifted and refuse a boot over a change that\n * is purely additive.\n */\nexport const searchColumnStamps = (spec: SearchColumnSpec): SearchColumnStamp[] => {\n const stamp = (column: string, expression: string): SearchColumnStamp => {\n const fingerprint = searchExpressionFingerprint(expression);\n return {\n column,\n expression,\n fingerprint,\n sql: `COMMENT ON COLUMN \"${spec.schema}\".\"${spec.table}\".\"${column}\" IS ${quote(fingerprint)};`\n };\n };\n const stamps = [stamp(spec.column, spec.expression)];\n if (spec.fuzzy) stamps.push(stamp(spec.fuzzy.column, spec.fuzzy.expression));\n return stamps;\n};\n\n/**\n * The same drift check as the boot ensure, for the SQL file.\n *\n * Needed because {@link searchColumnStamps} would otherwise *launder* drift on\n * the migration path: `ADD COLUMN IF NOT EXISTS` does nothing to a column that\n * exists, so a re-generated `search.sql` would stamp a stale column with the\n * new block's fingerprint and the next boot would find them in agreement.\n * Guarding first means the file refuses instead — `rebase db push` is attended,\n * and the operator reading the failure is the person who changed the block.\n */\nexport const searchStampGuards = (spec: SearchColumnSpec): string[] =>\n searchColumnStamps(spec).map(stamp => {\n const relation = quote(`\"${spec.schema}\".\"${spec.table}\"`);\n return `DO $rebase_search$\nDECLARE recorded text;\nBEGIN\n SELECT col_description(a.attrelid, a.attnum) INTO recorded\n FROM pg_attribute a\n WHERE a.attrelid = ${relation}::regclass AND a.attname = ${quote(stamp.column)} AND NOT a.attisdropped;\n IF recorded LIKE ${quote(`${SEARCH_STAMP_PREFIX}%`)} AND recorded <> ${quote(stamp.fingerprint)} THEN\n RAISE EXCEPTION 'Rebase: the search block for ${spec.schema}.${spec.table} changed after the generated column \"${stamp.column}\" was built (recorded %, expected ${stamp.fingerprint}). Postgres cannot alter a generated expression in place. Drop the column and re-apply this file — it rewrites the table and rebuilds the index: ALTER TABLE ${relation.slice(1, -1)} DROP COLUMN \"${stamp.column}\";', recorded;\n END IF;\nEND\n$rebase_search$;`;\n });\n\n/**\n * The index names the spec creates.\n *\n * Needed by name, not just by statement, so Atlas can be told to exclude them\n * from its diff — see `searchExcludePatterns`.\n */\nexport const searchIndexNames = (spec: SearchColumnSpec): string[] =>\n spec.fuzzy ? [spec.indexName, spec.fuzzy.indexName] : [spec.indexName];\n\n// ── Keeping the generated columns out of responses ──────────────────────────\n\n/**\n * The generated column names a collection's search block adds, if any.\n *\n * These are physical columns on the table, so `SELECT *` returns them. They are\n * an index in column form — a list of lexeme positions, or a concatenation of\n * every searchable field on the row — and nothing outside the query planner has\n * any use for them. Left in, every list response carries a second, larger copy\n * of the row's text.\n */\nexport const searchColumnNames = (collection: CollectionConfig): string[] => {\n let spec: SearchColumnSpec | undefined;\n try {\n spec = buildSearchColumnSpec(collection);\n } catch {\n // A malformed block is reported at boot, loudly. A read is the wrong\n // place to raise it a second time, and returning the row without the\n // exclusion would be worse than returning it with.\n return [];\n }\n if (!spec) return [];\n return spec.fuzzy ? [spec.column, spec.fuzzy.column] : [spec.column];\n};\n\n/**\n * True for a column whose type only ever holds a search index.\n *\n * Independent of any collection config on purpose: an introspected database\n * (BaaS mode) can carry a `tsvector` column this framework never created —\n * Pagila's `film.fulltext` is the canonical one — and it should not be returned\n * to callers either. `isDerivedIndexColumn` already keeps such a column out of\n * the *properties*; this keeps it out of the *rows*.\n */\nexport const isSearchIndexColumn = (column: { getSQLType?: () => string }): boolean => {\n const sqlType = typeof column?.getSQLType === \"function\" ? column.getSQLType().toLowerCase() : \"\";\n return sqlType === \"tsvector\" || sqlType === \"tsquery\";\n};\n\n/**\n * A drizzle select projection over `table` with the search columns dropped.\n *\n * Returns undefined when nothing needs dropping, so the common case keeps using\n * a plain `select()` and this stays invisible in the generated SQL.\n */\nexport const visibleColumnProjection = (\n tableColumns: Record<string, { getSQLType?: () => string }> | undefined,\n collection?: CollectionConfig\n): Record<string, unknown> | undefined => {\n const excluded = excludedColumnNames(tableColumns, collection);\n if (!tableColumns || excluded.length === 0) return undefined;\n const projection: Record<string, unknown> = {};\n for (const [name, column] of Object.entries(tableColumns)) {\n if (!excluded.includes(name)) projection[name] = column;\n }\n return projection;\n};\n\n/** The same exclusion as a drizzle `db.query` `columns` denylist. */\nexport const hiddenColumnsOption = (\n tableColumns: Record<string, { getSQLType?: () => string }> | undefined,\n collection?: CollectionConfig\n): Record<string, false> | undefined => {\n const excluded = excludedColumnNames(tableColumns, collection);\n if (excluded.length === 0) return undefined;\n return Object.fromEntries(excluded.map(name => [name, false as const]));\n};\n\n/**\n * The columns to keep out of a response, by name.\n *\n * `tableColumns` is whatever `getTableColumns` returned, which is `undefined`\n * for anything that is not a real drizzle table — a stub in a test, a derived\n * or nested path with no table behind it. Nothing to exclude is the right\n * answer there, and it has to be an answer rather than a throw: this runs on\n * the read path of every collection, opted in or not.\n */\nconst excludedColumnNames = (\n tableColumns: Record<string, { getSQLType?: () => string }> | undefined,\n collection?: CollectionConfig\n): string[] => {\n if (!tableColumns || typeof tableColumns !== \"object\") return [];\n const byName = new Set(collection ? searchColumnNames(collection) : []);\n return Object.keys(tableColumns).filter(\n name => byName.has(name) || isSearchIndexColumn(tableColumns[name])\n );\n};\n","import { CollectionConfig, NumberProperty, Property, ResolvedRelation, RelationProperty, SecurityOperation, SecurityRule, StringProperty, isPostgresCollectionConfig, DateProperty, ArrayProperty, MapProperty, ReferenceProperty, VectorProperty, BinaryProperty, isManyToMany, type ResolvedManyToMany, type ResolvedBelongsTo } from \"@rebasepro/types\";\nimport { getEnumVarName, getTableName, resolveCollectionRelations, findRelation, securityRuleToConditions, policyToPostgres, getEffectiveSecurityRules, getInjectedSecurityRules, resolveJunctionSpecs, getJunctionSecurityRules, getJunctionCollectionConfig, resolveStringColumnLength, relationalCollections } from \"@rebasepro/common\";\nimport { toSnakeCase, getPolicyNamesForRule, generateForeignKeyName, legacyForeignKeyName, toPostgresIdentifier } from \"@rebasepro/utils\";\nimport { AUTH_USERS_COLUMNS, authUsersColumnDefinition, authUsersColumnSql, isAuthCollection } from \"./auth-users-columns\";\nimport {\n buildSearchColumnSpec,\n searchColumnDefinition,\n fuzzyColumnDefinition,\n searchIndexStatements,\n searchHelperFunctions,\n searchExtensionStatements,\n searchColumnNames,\n searchColumnStamps,\n searchStampGuards,\n searchIndexNames,\n type SearchColumnSpec\n} from \"./search-column\";\nimport {\n buildVectorColumnSpecs,\n buildVectorIndexPlan,\n vectorColumnDefinition,\n vectorDimensionGuard,\n vectorExtensionDeclared,\n vectorExtensionStatement,\n vectorIndexStatements,\n vectorObjectNames,\n VECTOR_EXTENSION_OPT_IN,\n type VectorIndexPlan\n} from \"./vector-index\";\nimport { buildCollectionIndexSpecs, collectionIndexStatements } from \"./collection-index\";\nimport { REBASE_SCHEMA } from \"@rebasepro/types\";\n\n// --- Helper Functions ---\n\nexport const resolveColumnName = (propName: string, prop?: Property | null): string => {\n if (prop && \"columnName\" in prop && typeof prop.columnName === \"string\") {\n return prop.columnName;\n }\n return toSnakeCase(propName);\n};\n\nexport const getPrimaryKeyProp = (collection: CollectionConfig): { name: string, type: \"string\" | \"number\", isUuid: boolean } => {\n if (collection.properties) {\n const idPropEntry = Object.entries(collection.properties).find(([_, prop]) => \"isId\" in (prop as unknown as object) && Boolean((prop as unknown as Record<string, unknown>).isId));\n if (idPropEntry) {\n const prop = idPropEntry[1] as unknown as Property;\n const isUuid = prop.type === \"string\" && \"isId\" in prop && (prop as unknown as StringProperty).isId === \"uuid\";\n return { name: idPropEntry[0], type: prop.type === \"number\" ? \"number\" : \"string\", isUuid };\n }\n }\n const idProp = collection.properties?.[\"id\"] as unknown as Property | undefined;\n if (idProp?.type === \"number\") {\n return { name: \"id\", type: \"number\", isUuid: false };\n }\n const isUuid = idProp?.type === \"string\" && \"isId\" in idProp && (idProp as unknown as StringProperty).isId === \"uuid\";\n return { name: \"id\", type: \"string\", isUuid: isUuid ?? false };\n};\n\nexport const isNumericId = (collection: CollectionConfig): boolean => {\n return getPrimaryKeyProp(collection).type === \"number\";\n};\n\nexport const getPrimaryKeyName = (collection: CollectionConfig): string => {\n return getPrimaryKeyProp(collection).name;\n};\n\n/** The column type a junction holds for one endpoint's primary key. */\nconst junctionKeyType = (collection: CollectionConfig): string =>\n isNumericId(collection) ? \"INTEGER\" : (getPrimaryKeyProp(collection).isUuid ? \"UUID\" : \"TEXT\");\n\nexport const isIdProperty = (propName: string, prop: Property, collection: CollectionConfig): boolean => {\n if (\"isId\" in prop && Boolean(prop.isId)) return true;\n const hasExplicitId = Object.values(collection.properties ?? {}).some(p => \"isId\" in (p as unknown as object) && Boolean((p as unknown as Record<string, unknown>).isId));\n return !hasExplicitId && propName === \"id\";\n};\n\n\ntype ResolveCollection = (slug: string) => CollectionConfig | undefined;\n\n/**\n * Render statements produced by {@link generatePolicyStatements} back into the\n * exact string the DDL/policies files have always carried: each statement on\n * its own line, terminated by a newline. Keeping the string form derived from\n * the statement array means the two can never drift — the boot-time applier and\n * the generated `policies.sql` emit the same SQL, from the same source.\n */\nconst statementsToDdl = (statements: string[]): string => statements.map(s => `${s}\\n`).join(\"\");\n\n/**\n * The `ON DELETE` a foreign key gets when the author did not say.\n *\n * One rule for every property that emits one — `belongsTo` relations and\n * `reference` properties alike. `reference` kept its own `CASCADE` default for\n * a release after this rule was written, which is exactly the split this\n * function exists to prevent: two spellings of the same link, two data-retention\n * behaviours, and only one of them documented.\n *\n * An optional link is `SET NULL`: the column can hold NULL, so dropping the\n * parent leaves the child row with an empty pointer, which is what \"optional\"\n * already means.\n *\n * A required link is **`RESTRICT`**, not `CASCADE`. `NOT NULL` says the child\n * cannot exist without a parent; it does not say deleting the parent should\n * take the child with it. That second claim is a data-retention decision, and\n * defaulting to it meant `onDelete` — a field nobody has to write — silently\n * turned every `DELETE FROM authors` into a cascade through posts, comments and\n * anything else that hung off them. `RESTRICT` fails the delete instead and\n * names the constraint, so the author chooses `cascade` on purpose or clears\n * the children first.\n *\n * Both DDL paths — the `CREATE TABLE` generator and {@link planRelationalColumns},\n * which boot-ensure compares the live database against — call this, so a\n * default that appears in one and not the other cannot make `db push` plan a\n * constraint rewrite on every run.\n */\nexport const defaultBelongsToOnDelete = (required: boolean | undefined): \"RESTRICT\" | \"SET NULL\" =>\n required ? \"RESTRICT\" : \"SET NULL\";\n\nconst generatePolicyDdl = (collection: CollectionConfig, rule: SecurityRule, resolveCollection: ResolveCollection): string =>\n statementsToDdl(generatePolicyStatements(collection, rule, resolveCollection));\n\n/**\n * The individual SQL statements a single security rule compiles to: a\n * `DROP POLICY IF EXISTS` / `CREATE POLICY` pair per operation, each a complete\n * statement (terminated by `;`, no trailing newline).\n *\n * This is the primitive the boot-time RLS applier runs one statement at a time\n * (the runtime's DB handle speaks the extended query protocol, which forbids\n * multiple commands in one execute), while `db push` writes the joined string.\n */\nexport const generatePolicyStatements = (collection: CollectionConfig, rule: SecurityRule, resolveCollection: ResolveCollection): string[] => {\n const tableName = getTableName(collection);\n const ops: readonly SecurityOperation[] = rule.operations && rule.operations.length > 0\n ? rule.operations\n : [rule.operation ?? \"all\"];\n\n const policyNames = getPolicyNamesForRule(rule, tableName);\n\n return ops.flatMap((op, opIdx) => {\n return generateSinglePolicyStatements(collection, rule, op, policyNames[opIdx], resolveCollection);\n });\n};\n\nconst generateSinglePolicyStatements = (collection: CollectionConfig, rule: SecurityRule, operation: SecurityOperation, policyName: string, resolveCollection: ResolveCollection): string[] => {\n const schema = isPostgresCollectionConfig(collection) && collection.schema ? collection.schema : \"public\";\n const tableName = getTableName(collection);\n const mode = (rule.mode ?? \"permissive\").toUpperCase();\n const operationUpper = operation.toUpperCase();\n const pgRoles = rule.pgRoles ? [...rule.pgRoles].sort() : [\"public\"];\n\n const needsUsing = operation !== \"insert\";\n const needsWithCheck = operation !== \"select\" && operation !== \"delete\";\n\n // Desugar the rule (access / ownerField / roles / structured condition / raw\n // SQL) into the shared PolicyExpression model, then compile to SQL. This is\n // the same normalization the client-side evaluator uses, so DDL and UI agree.\n const { usingExpr, withCheckExpr } = securityRuleToConditions(rule);\n\n let usingClause = needsUsing && usingExpr ? policyToPostgres(usingExpr, collection, { resolveCollection }) : null;\n let withCheckClause = needsWithCheck && withCheckExpr ? policyToPostgres(withCheckExpr, collection, { resolveCollection }) : null;\n\n if (!usingClause && needsUsing) {\n usingClause = \"false\";\n }\n if (!withCheckClause && needsWithCheck) {\n withCheckClause = \"false\";\n }\n\n const drop = `DROP POLICY IF EXISTS \"${policyName}\" ON \"${schema}\".\"${tableName}\";`;\n let create = `CREATE POLICY \"${policyName}\" ON \"${schema}\".\"${tableName}\" AS ${mode} FOR ${operationUpper} TO ${pgRoles.map(r => `\"${r}\"`).join(\", \")}`;\n if (usingClause) create += ` USING (${usingClause})`;\n if (withCheckClause) create += ` WITH CHECK (${withCheckClause})`;\n create += \";\";\n return [drop, create];\n};\n\n/**\n * Single-quote escaping for a SQL string literal (PostgreSQL doubles the\n * quote). Enum labels come straight from user-authored collection config, so a\n * label like `it's` closes the literal early and the whole generated file stops\n * parsing at the `CREATE TYPE`. Lives here rather than next to its other caller\n * because ensure-collection-tables already imports from this module — the\n * reverse would be a cycle.\n */\nexport const quoteSqlLiteral = (value: string): string => `'${value.replace(/'/g, \"''\")}'`;\n\nexport const getSqlColumnType = (propName: string, prop: Property, collection: CollectionConfig, collections: CollectionConfig[]): string => {\n switch (prop.type) {\n case \"string\": {\n const stringProp = prop as StringProperty;\n if (stringProp.enum) {\n const tableName = getTableName(collection);\n const colName = resolveColumnName(propName, prop);\n const schema = isPostgresCollectionConfig(collection) && collection.schema ? collection.schema : \"public\";\n return `\"${schema}\".\"${tableName}_${colName}\"`;\n }\n if (stringProp.isId === \"uuid\" || stringProp.columnType === \"uuid\") {\n return \"UUID\";\n }\n // Width comes from `validation.max` when the property states one.\n // It used to be a hardcoded 255 here and *absent* on the Drizzle\n // path, so the same property produced a bounded column down one\n // generator and an unbounded one down the other.\n if (stringProp.columnType === \"char\") {\n return `CHAR(${resolveStringColumnLength(stringProp)})`;\n }\n if (stringProp.columnType === \"varchar\") {\n return `VARCHAR(${resolveStringColumnLength(stringProp)})`;\n }\n // `text` is the default. The two generators disagreed here before:\n // this one emitted VARCHAR(255) while the drizzle path emitted a bare\n // `varchar()`, which Postgres treats as unbounded — so the same\n // property produced a capped column down one path and an uncapped one\n // down the other.\n return \"TEXT\";\n }\n case \"number\": {\n const numProp = prop as NumberProperty;\n const isId = isIdProperty(propName, prop, collection);\n if (\"isId\" in numProp && numProp.isId === \"increment\") {\n return \"INTEGER GENERATED BY DEFAULT AS IDENTITY\";\n }\n if (numProp.columnType) {\n if (numProp.columnType === \"double precision\") return \"DOUBLE PRECISION\";\n return numProp.columnType.toUpperCase();\n }\n return (numProp.validation?.integer || isId) ? \"INTEGER\" : \"NUMERIC\";\n }\n case \"boolean\":\n return \"BOOLEAN\";\n case \"date\": {\n const dateProp = prop as DateProperty;\n if (dateProp.columnType === \"date\") return \"DATE\";\n if (dateProp.columnType === \"time\") return \"TIME\";\n return \"TIMESTAMP WITH TIME ZONE\";\n }\n case \"map\": {\n const mapProp = prop as MapProperty;\n return mapProp.columnType === \"json\" ? \"JSON\" : \"JSONB\";\n }\n // `{ latitude, longitude }` — a document, like `map`. It used to fall to\n // the `TEXT` default below while the Drizzle generator emitted no column\n // at all, which is the divergence that made the type unpersistable.\n case \"geopoint\":\n return \"JSONB\";\n case \"array\": {\n const arrayProp = prop as ArrayProperty;\n let colType = arrayProp.columnType;\n if (!colType && arrayProp.of && !Array.isArray(arrayProp.of)) {\n const ofProp = arrayProp.of as Property;\n if (ofProp.type === \"string\") {\n colType = \"text[]\";\n } else if (ofProp.type === \"number\") {\n colType = ofProp.validation?.integer ? \"integer[]\" : \"numeric[]\";\n } else if (ofProp.type === \"boolean\") {\n colType = \"boolean[]\";\n }\n }\n if (colType === \"json\") return \"JSON\";\n if (colType === \"text[]\") return \"TEXT[]\";\n if (colType === \"integer[]\") return \"INTEGER[]\";\n if (colType === \"boolean[]\") return \"BOOLEAN[]\";\n if (colType === \"numeric[]\") return \"NUMERIC[]\";\n return \"JSONB\";\n }\n case \"vector\": {\n const vp = prop as VectorProperty;\n return `VECTOR(${vp.dimensions})`;\n }\n case \"binary\": {\n return \"BYTEA\";\n }\n case \"relation\": {\n const refProp = prop as RelationProperty;\n const resolvedRelations = resolveCollectionRelations(collection);\n const relation = findRelation(resolvedRelations, refProp.relation?.relationName ?? propName);\n if (relation?.kind !== \"belongsTo\") {\n throw new Error(`Relation ${propName} does not put a column on this table (only \\`belongsTo\\` does)`);\n }\n let targetCollection: CollectionConfig;\n try {\n targetCollection = relation.target();\n } catch {\n return \"TEXT\";\n }\n const pkProp = getPrimaryKeyProp(targetCollection);\n return pkProp.type === \"number\" ? \"INTEGER\" : (pkProp.isUuid ? \"UUID\" : \"TEXT\");\n }\n case \"reference\": {\n const refProp = prop as ReferenceProperty;\n const targetCollection = collections.find(c => c.slug === refProp.path || getTableName(c) === refProp.path);\n if (!targetCollection) return \"TEXT\";\n const pkProp = getPrimaryKeyProp(targetCollection);\n return pkProp.type === \"number\" ? \"INTEGER\" : (pkProp.isUuid ? \"UUID\" : \"TEXT\");\n }\n default:\n // A silent `TEXT` here is how the two generators drifted apart: the\n // database got a column for `geopoint` and the Drizzle table did\n // not, so the type looked supported and persisted nothing. Every\n // member of `DataType` is handled above; anything else is a\n // generator that has not been taught the type yet, and it should\n // say so rather than invent a column.\n throw new Error(\n `No Postgres column type for property '${propName}' of type ` +\n `'${(prop as Property).type}' in collection '${collection.slug}'. ` +\n \"Add a case to `getSqlColumnType` (and to `getDrizzleColumn`, which must agree).\"\n );\n }\n};\n\n/**\n * Everything a `search` block needs, as a file Rebase applies itself.\n *\n * Search is the one part of the schema Atlas does not own. Two independent\n * reasons, and either alone would be enough:\n *\n * 1. Its free tier refuses to *parse* a desired-state file that so much as\n * contains a function — \"functions and procedures are available to\n * logged-in users only\". A generated `tsvector` column cannot avoid one:\n * `unaccent` is STABLE and jsonb flattening needs a set-returning function,\n * so both have to be wrapped in an IMMUTABLE helper to be legal in a\n * generated column at all.\n * 2. Even with the file accepted, Atlas *wipes* the dev database it diffs\n * against, so a helper seeded there beforehand is gone by the time the plan\n * is analysed. There is no hook to reinstate it.\n *\n * So the column, its index and its helpers are excluded from Atlas's view\n * (`searchExcludePatterns`) and applied from here — the same arrangement the\n * RLS policies already use, and for the same underlying reason.\n *\n * Ordered as it must run: extensions, then helpers, then the column whose\n * expression calls them, then the index over that column. Every statement is\n * `IF NOT EXISTS` / `OR REPLACE`, because this is replayed on every push and\n * appended to migrations that run against databases at any stage of their\n * life. Empty when nothing opted in — the caller writes no file then.\n *\n * The dev database Atlas plans against needs none of this. `searchExcludePatterns`\n * removes the search column from the *inspected* state, so the state Atlas\n * replays there never mentions the column and never calls a helper. Seeding the\n * helpers into that database was tried and removed: measured on Atlas 1.2.3, a\n * `schema apply --dry-run` produces byte-identical output seeded or not, and\n * Atlas wipes the dev database before it plans anyway — so a seed could not have\n * helped even where the excludes are missing, which is the case that really does\n * fail with `function public.rebase_search_unaccent(text) does not exist`.\n */\nexport const generatePostgresSearchDdl = (allCollections: CollectionConfig[]): string => {\n const collections = relationalCollections(allCollections);\n const specs = collections\n .map(c => buildSearchColumnSpec(c))\n .filter((s): s is SearchColumnSpec => s !== undefined);\n if (specs.length === 0) return \"\";\n\n const extensions = Array.from(new Set(specs.flatMap(searchExtensionStatements)));\n const helpers = Array.from(new Set(specs.flatMap(searchHelperFunctions)));\n\n let ddl = \"-- This file is auto-generated by the Rebase DDL generator. Do not edit manually.\\n\";\n ddl += \"--\\n\";\n ddl += \"-- Full-text search for the collections declaring a `search` block.\\n\";\n ddl += \"-- Applied by Rebase, not by Atlas — see generatePostgresSearchDdl.\\n\\n\";\n\n extensions.forEach(s => { ddl += `${s}\\n`; });\n if (extensions.length > 0) ddl += \"\\n\";\n helpers.forEach(s => { ddl += `${s}\\n\\n`; });\n\n for (const collection of collections) {\n const spec = buildSearchColumnSpec(collection);\n if (!spec) continue;\n const schema = isPostgresCollectionConfig(collection) && collection.schema ? collection.schema : \"public\";\n const table = `\"${schema}\".\"${getTableName(collection)}\"`;\n\n // ADD COLUMN IF NOT EXISTS rather than the inline definition the\n // CREATE TABLE would use: by the time this runs the table exists,\n // whether Atlas just created it or it has been live for a year.\n // Refuse before altering anything if the column in the database was\n // generated from a different `search` block: `ADD COLUMN IF NOT EXISTS`\n // is a no-op against an existing column, so without this the file would\n // report success and leave the old expression in place — and then stamp\n // it as current.\n searchStampGuards(spec).forEach(s => { ddl += `${s}\\n`; });\n ddl += `ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${searchColumnDefinition(spec)};\\n`;\n const fuzzyDef = fuzzyColumnDefinition(spec);\n if (fuzzyDef) ddl += `ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${fuzzyDef};\\n`;\n // The fingerprint of the expression each column was built from — the\n // only record of *which* block a generated column came from, and what\n // the guard above and the boot-time ensure both compare against.\n searchColumnStamps(spec).forEach(s => { ddl += `${s.sql}\\n`; });\n searchIndexStatements(spec).forEach(s => { ddl += `${s}\\n`; });\n ddl += \"\\n\";\n }\n\n return ddl;\n};\n\n/**\n * Glob patterns telling Atlas to leave the search column and its index alone.\n *\n * Without these, a desired state that omits search reads to Atlas as an\n * instruction to drop the column — taking the index and the whole search\n * feature with it on the next push.\n *\n * Fully qualified, `schema.table.object`, matching the include list. The\n * two-part form is what Atlas wants when the connection URL scopes it to one\n * schema and is *silently ignored* otherwise: it reads `posts.search_vector`\n * as a table named `search_vector` in a schema named `posts`, matches nothing,\n * and reports no error for the pattern that never fired.\n */\nexport const searchExcludePatterns = (allCollections: CollectionConfig[]): string[] => {\n const patterns: string[] = [];\n for (const collection of relationalCollections(allCollections)) {\n const spec = buildSearchColumnSpec(collection);\n if (!spec) continue;\n const schema = isPostgresCollectionConfig(collection) && collection.schema ? collection.schema : \"public\";\n const table = getTableName(collection);\n for (const name of searchColumnNames(collection)) {\n patterns.push(`${schema}.${table}.${name}`);\n }\n for (const index of searchIndexNames(spec)) {\n patterns.push(`${schema}.${table}.${index}`);\n }\n }\n return patterns;\n};\n\n/**\n * Everything a `{ type: \"vector\" }` property needs, as a file Rebase applies\n * itself.\n *\n * Vector is the second part of the schema Atlas does not own, and it is out for\n * a narrower reason than search: not that Atlas refuses to parse the file, but\n * that it cannot *execute* it. To compute a desired state Atlas materialises\n * `schema.sql` in a dev database, that database is created empty, and Atlas\n * empties it again at the start of every run — so `VECTOR(384)` resolves\n * against a database that structurally cannot have pgvector, and every push\n * dies with `type \"vector\" does not exist`. Seeding the extension beforehand\n * does not survive the clean (measured: present before the run, gone after),\n * putting `CREATE EXTENSION` in `schema.sql` is refused as an Atlas Pro\n * feature, and `--exclude` alone cannot help because Atlas still parses and\n * applies the whole file.\n *\n * So the column, its ANN indexes and the extension are excluded from Atlas's\n * view ({@link vectorExcludePatterns}) and applied from here — the same\n * arrangement search and the RLS policies already use.\n *\n * `CREATE EXTENSION` appears only when the project's database named `vector` in\n * its `extensions` — see `DatabaseOptions.extensions`. Everything else is\n * emitted either way: a database that already has pgvector installed by hand\n * needs no permission from anyone, and one that does not gets the column\n * refused by Postgres with a message this repo makes name the opt-in.\n *\n * Ordered as it must run: the extension, then the drift guard, then the column\n * whose type needs the extension, then the indexes over that column. Every\n * statement is `IF NOT EXISTS`, because this is replayed on every push and\n * appended to migrations that run against databases at any stage of their life.\n * Empty when no collection declares a vector — the caller writes no file then.\n */\nexport const generatePostgresVectorDdl = (\n allCollections: CollectionConfig[],\n options: { extensions?: readonly string[] } = {}\n): string => {\n const collections = relationalCollections(allCollections);\n const withVectors = collections.filter(c => buildVectorColumnSpecs(c, resolveColumnName).length > 0);\n if (withVectors.length === 0) return \"\";\n\n let ddl = \"-- This file is auto-generated by the Rebase DDL generator. Do not edit manually.\\n\";\n ddl += \"--\\n\";\n ddl += \"-- Vector columns and their ANN indexes, for the collections declaring a\\n\";\n ddl += \"-- `vector` property. Applied by Rebase, not by Atlas — see\\n\";\n ddl += \"-- generatePostgresVectorDdl.\\n\\n\";\n // The note is not decoration. Someone reading this file after a failed push\n // is looking for the reason the type does not exist, and \"no CREATE\n // EXTENSION here\" is invisible unless it is written down.\n ddl += vectorExtensionDeclared(options.extensions)\n ? `${vectorExtensionStatement()}\\n\\n`\n : `-- pgvector is not installed from here: no database declared it. To let Rebase\\n` +\n `-- install it, add ${VECTOR_EXTENSION_OPT_IN} in config/resources.ts.\\n` +\n `-- Otherwise install it once by hand; the column below needs the type either way.\\n\\n`;\n\n for (const collection of withVectors) {\n const table = `\"${isPostgresCollectionConfig(collection) && collection.schema ? collection.schema : \"public\"}\".\"${getTableName(collection)}\"`;\n for (const spec of buildVectorColumnSpecs(collection, resolveColumnName)) {\n // Guard before the ADD, so a changed `dimensions` is reported rather\n // than skipped over — see `vectorDimensionGuard`.\n ddl += `${vectorDimensionGuard(spec)}\\n`;\n // ADD COLUMN IF NOT EXISTS rather than the inline definition the\n // CREATE TABLE would use: by the time this runs the table exists,\n // whether Atlas just created it or it has been live for a year.\n ddl += `ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${vectorColumnDefinition(spec)};\\n`;\n }\n const plan = buildVectorIndexPlan(collection, resolveColumnName);\n vectorIndexStatements(plan).forEach(s => { ddl += `${s}\\n`; });\n for (const skip of plan.skipped) {\n ddl += `-- No ANN index on \"${skip.schema}\".\"${skip.table}\".\"${skip.column}\": ${skip.reason}\\n`;\n }\n ddl += \"\\n\";\n }\n\n return ddl;\n};\n\n/**\n * Glob patterns telling Atlas to leave the vector column and its ANN indexes\n * alone.\n *\n * Without these, a desired state that omits the column reads to Atlas as an\n * instruction to drop it, taking the embeddings and the indexes with it on the\n * next push.\n *\n * Fully qualified, `schema.table.object`, for the reason spelled out on\n * {@link searchExcludePatterns}: the two-part form matches nothing and reports\n * no error, which turns a protection into a `DROP COLUMN`.\n */\nexport const vectorExcludePatterns = (allCollections: CollectionConfig[]): string[] => {\n const patterns: string[] = [];\n for (const collection of relationalCollections(allCollections)) {\n const schema = isPostgresCollectionConfig(collection) && collection.schema ? collection.schema : \"public\";\n const table = getTableName(collection);\n for (const name of vectorObjectNames(collection, resolveColumnName)) {\n patterns.push(`${schema}.${table}.${name}`);\n }\n }\n return patterns;\n};\n\nexport const generatePostgresDdl = async (\n allCollections: CollectionConfig[],\n options: { includePolicies?: boolean; includeSearch?: boolean; includeVector?: boolean } = {\n includePolicies: true,\n includeSearch: true,\n includeVector: true\n }\n): Promise<string> => {\n // Only the collections this engine stores. See `relationalCollections`.\n const collections = relationalCollections(allCollections);\n let ddl = \"-- This file is auto-generated by the Rebase DDL generator. Do not edit manually.\\n\\n\";\n\n // 1. Create custom schemas.\n //\n // `rebase` is unconditional and load-bearing. The RLS helper functions live\n // in it, so the migration preamble creates it — which puts it in Atlas's\n // replayed state, and anything in that state but absent from this desired\n // schema gets a `DROP SCHEMA … CASCADE` planned against it. That would take\n // the auth tables. It used to appear here only when some collection\n // happened to declare `schema: \"rebase\"`; the scaffold's users collection\n // does, which is why nobody hit it, but nothing guaranteed it.\n const uniqueSchemas = Array.from(new Set([\n REBASE_SCHEMA,\n ...collections.map(c => isPostgresCollectionConfig(c) ? c.schema : undefined).filter(Boolean)\n ]));\n uniqueSchemas.forEach(schema => {\n if (schema) ddl += `CREATE SCHEMA IF NOT EXISTS \"${schema}\";\\n`;\n });\n if (uniqueSchemas.length > 0) ddl += \"\\n\";\n\n // 1b. Search support, for collections that opted in.\n //\n // Extensions and helper functions come before every CREATE TABLE because a\n // generated column's expression is resolved at creation time: a table whose\n // search column calls `rebase_search_text` cannot be created before that\n // function exists. Both are `IF NOT EXISTS` / `OR REPLACE`, so a file\n // replayed against a live database is a no-op here.\n const searchSpecs = collections\n .map(c => buildSearchColumnSpec(c))\n .filter((s): s is SearchColumnSpec => s !== undefined);\n\n if (searchSpecs.length > 0) {\n if (options.includeSearch === false) {\n // Everything search-related lives in `search.sql` — see\n // `generatePostgresSearchDdl` for why Atlas is not shown any of it.\n ddl += \"-- Full-text search support lives in `search.sql`, applied separately.\\n\\n\";\n } else {\n const extensions = Array.from(new Set(searchSpecs.flatMap(searchExtensionStatements)));\n const helpers = Array.from(new Set(searchSpecs.flatMap(searchHelperFunctions)));\n ddl += \"-- Full-text search support (collections declaring a `search` block)\\n\";\n extensions.forEach(s => { ddl += `${s}\\n`; });\n if (extensions.length > 0) ddl += \"\\n\";\n helpers.forEach(s => { ddl += `${s}\\n\\n`; });\n }\n }\n\n // 1c. Vector support, for collections that declared a `vector` property.\n //\n // Only a note, never a statement — unlike 1b, which emits search's\n // extensions when they are included. `CREATE EXTENSION vector` is the one\n // thing this file must never carry whichever way the flag goes: Atlas\n // rejects an extension in a desired state as a paid feature. The note is\n // for whoever opens the generated file looking for the column.\n if (options.includeVector === false\n && collections.some(c => buildVectorColumnSpecs(c, resolveColumnName).length > 0)) {\n ddl += \"-- Vector columns and their ANN indexes live in `vector.sql`, applied separately.\\n\\n\";\n }\n\n // 2. Generate Enums\n //\n // The enum type name is derived from table + column, so two collections\n // mapped onto the same table — or two properties whose `columnName`\n // resolves to the same column — land on the same name. `CREATE TYPE` has no\n // IF NOT EXISTS, so emitting it twice aborts the whole file on the second\n // statement. Dedupe by name, matching planCollectionSchemaEnsure.\n const emittedEnums = new Set<string>();\n collections.forEach(collection => {\n const collectionTable = getTableName(collection);\n const schema = isPostgresCollectionConfig(collection) && collection.schema ? collection.schema : \"public\";\n Object.entries(collection.properties ?? {}).forEach(([propName, prop]) => {\n if ((\"enum\" in prop) && (prop.type === \"string\" || prop.type === \"number\") && prop.enum) {\n const enumDbName = `${collectionTable}_${resolveColumnName(propName, prop)}`;\n const values = Array.isArray(prop.enum)\n ? (prop.enum as (string | number | { id: string | number })[]).map((v: string | number | { id: string | number }) =>\n String(typeof v === \"object\" && v !== null && \"id\" in v ? v.id : v)\n )\n : Object.keys(prop.enum);\n if (values.length > 0 && !emittedEnums.has(`${schema}.${enumDbName}`)) {\n emittedEnums.add(`${schema}.${enumDbName}`);\n ddl += `CREATE TYPE \"${schema}\".\"${enumDbName}\" AS ENUM (${values.map(quoteSqlLiteral).join(\", \")});\\n`;\n }\n }\n });\n });\n if (ddl.endsWith(\";\\n\")) ddl += \"\\n\";\n\n // Junction policy derivation needs every declaring side of each junction,\n // not just the first relation that reached it in the walk below.\n const junctionSpecs = resolveJunctionSpecs(collections);\n\n const allTablesToGenerate = new Map<string, {\n collection: CollectionConfig,\n isJunction?: boolean,\n relation?: ResolvedRelation,\n sourceCollection?: CollectionConfig\n }>();\n\n // Identify all tables\n for (const collection of collections) {\n const tableName = getTableName(collection);\n if (tableName) {\n allTablesToGenerate.set(tableName, { collection });\n }\n\n const resolvedRelations = resolveCollectionRelations(collection);\n for (const relation of Object.values(resolvedRelations)) {\n if (isManyToMany(relation)) {\n const junctionTableName = relation.through.table;\n if (!allTablesToGenerate.has(junctionTableName)) {\n allTablesToGenerate.set(junctionTableName, {\n collection: {\n table: junctionTableName,\n properties: {}\n } as CollectionConfig,\n isJunction: true,\n relation: relation,\n sourceCollection: collection\n });\n }\n }\n }\n }\n\n // 3. Generate tables\n const fkStatements: string[] = [];\n // Indexes follow the tables for the same reason the FK constraints do: the\n // table has to exist first.\n const indexStatements: string[] = [];\n // Policies are emitted after every CREATE TABLE, like the FK constraints:\n // a policy may reference other tables (a junction's derived policies always\n // reference both endpoints; `policy.existsIn` references a join table), and\n // CREATE POLICY validates those relations at creation time.\n const policyStatements: string[] = [];\n for (const [tableName, {\n collection,\n isJunction,\n relation,\n sourceCollection\n }] of allTablesToGenerate.entries()) {\n const schema = isPostgresCollectionConfig(collection) && collection.schema ? collection.schema : \"public\";\n const baseTableName = tableName.includes(\".\") ? tableName.split(\".\").pop()! : tableName;\n\n if (isJunction && relation && sourceCollection && isManyToMany(relation)) {\n const targetCollection = relation.target();\n const sourceTable = getTableName(sourceCollection);\n const targetTable = getTableName(targetCollection);\n const sourceSchema = isPostgresCollectionConfig(sourceCollection) && sourceCollection.schema ? sourceCollection.schema : \"public\";\n const targetSchema = isPostgresCollectionConfig(targetCollection) && targetCollection.schema ? targetCollection.schema : \"public\";\n const { sourceColumn, targetColumn } = relation.through;\n\n // TEXT, matching the string default: a junction column has to have the\n // same type as the primary key it references.\n const sourceColType = isNumericId(sourceCollection) ? \"INTEGER\" : (getPrimaryKeyProp(sourceCollection).isUuid ? \"UUID\" : \"TEXT\");\n const targetColType = isNumericId(targetCollection) ? \"INTEGER\" : (getPrimaryKeyProp(targetCollection).isUuid ? \"UUID\" : \"TEXT\");\n const sourceId = getPrimaryKeyName(sourceCollection);\n const targetId = getPrimaryKeyName(targetCollection);\n\n const onDelete = relation.onDelete ?? \"CASCADE\";\n\n ddl += `CREATE TABLE \"${schema}\".\"${baseTableName}\" (\\n`;\n ddl += ` \"${sourceColumn}\" ${sourceColType} NOT NULL,\\n`;\n ddl += ` \"${targetColumn}\" ${targetColType} NOT NULL,\\n`;\n ddl += ` PRIMARY KEY (\"${sourceColumn}\", \"${targetColumn}\")\\n`;\n ddl += `);\\n\\n`;\n\n fkStatements.push(`ALTER TABLE \"${schema}\".\"${baseTableName}\" ADD CONSTRAINT \"${toPostgresIdentifier(`${baseTableName}_${sourceColumn}_fkey`)}\" FOREIGN KEY (\"${sourceColumn}\") REFERENCES \"${sourceSchema}\".\"${sourceTable}\" (\"${sourceId}\") ON DELETE ${onDelete.toUpperCase()};`);\n fkStatements.push(`ALTER TABLE \"${schema}\".\"${baseTableName}\" ADD CONSTRAINT \"${toPostgresIdentifier(`${baseTableName}_${targetColumn}_fkey`)}\" FOREIGN KEY (\"${targetColumn}\") REFERENCES \"${targetSchema}\".\"${targetTable}\" (\"${targetId}\") ON DELETE ${onDelete.toUpperCase()};`);\n\n if (options.includePolicies) {\n // Junction tables are generated tables like any other: locked by\n // default, with derived policies — reads follow the endpoints'\n // visibility, writes follow the declaring side's update rules.\n // Without this they were the one kind of generated table with no\n // RLS at all, readable and writable by every signed-in user.\n ddl += `ALTER TABLE \"${schema}\".\"${baseTableName}\" ENABLE ROW LEVEL SECURITY;\\n`;\n ddl += `\\n`;\n\n const spec = junctionSpecs.get(baseTableName);\n if (spec) {\n const junctionCollection = getJunctionCollectionConfig(spec);\n const resolveCollection: ResolveCollection = (slug) => collections.find(c => c.slug === slug || getTableName(c) === slug);\n getJunctionSecurityRules(spec).forEach((rule: SecurityRule) => {\n policyStatements.push(generatePolicyDdl(junctionCollection, rule, resolveCollection));\n });\n }\n }\n } else if (!isJunction) {\n ddl += `CREATE TABLE \"${schema}\".\"${baseTableName}\" (\\n`;\n const columns: string[] = [];\n\n Object.entries(collection.properties ?? {}).forEach(([propName, prop]) => {\n // A vector column is not Atlas's — `vector.sql` adds it. Skipped\n // here rather than filtered upstream so the column keeps its\n // place in the generator's order when it *is* included, which is\n // what the derived-names contract renders.\n if (prop.type === \"vector\" && options.includeVector === false) {\n return;\n }\n if (prop.type === \"relation\") {\n const refProp = prop as RelationProperty;\n const resolvedRelations = resolveCollectionRelations(collection);\n const relInfo = findRelation(resolvedRelations, refProp.relation?.relationName ?? propName);\n\n if (relInfo?.kind !== \"belongsTo\") {\n return;\n }\n\n if (collection.properties[relInfo.localKey] && propName !== relInfo.localKey) {\n return;\n }\n\n let targetCollection: CollectionConfig;\n try {\n targetCollection = relInfo.target();\n } catch {\n return;\n }\n\n const targetTable = getTableName(targetCollection);\n const targetSchema = isPostgresCollectionConfig(targetCollection) && targetCollection.schema ? targetCollection.schema : \"public\";\n const targetId = getPrimaryKeyName(targetCollection);\n const fkColType = getSqlColumnType(propName, prop, collection, collections);\n \n const onUpdate = relInfo.onUpdate ? ` ON UPDATE ${relInfo.onUpdate.toUpperCase()}` : \"\";\n const required = prop.validation?.required;\n const onDeleteVal = relInfo.onDelete ?? defaultBelongsToOnDelete(required);\n \n let colDef = ` \"${relInfo.localKey}\" ${fkColType}`;\n if (required) colDef += \" NOT NULL\";\n columns.push(colDef);\n\n fkStatements.push(`ALTER TABLE \"${schema}\".\"${baseTableName}\" ADD CONSTRAINT \"${toPostgresIdentifier(`${baseTableName}_${relInfo.localKey}_fkey`)}\" FOREIGN KEY (\"${relInfo.localKey}\") REFERENCES \"${targetSchema}\".\"${targetTable}\" (\"${targetId}\") ON DELETE ${onDeleteVal.toUpperCase()}${onUpdate};`);\n } else if (prop.type === \"reference\") {\n const refProp = prop as ReferenceProperty;\n const targetCollection = collections.find(c => c.slug === refProp.path || getTableName(c) === refProp.path);\n const colName = resolveColumnName(propName, prop);\n const colType = getSqlColumnType(propName, prop, collection, collections);\n const required = prop.validation?.required;\n\n if (!targetCollection) {\n let colDef = ` \"${colName}\" ${colType}`;\n if (required) colDef += \" NOT NULL\";\n columns.push(colDef);\n } else {\n const targetTable = getTableName(targetCollection);\n const targetSchema = isPostgresCollectionConfig(targetCollection) && targetCollection.schema ? targetCollection.schema : \"public\";\n const targetId = getPrimaryKeyName(targetCollection);\n // The same rule as `belongsTo`, from the same function.\n // A `reference` is a foreign key like any other, and it\n // carries no `onDelete` of its own to override it with.\n const onDelete = defaultBelongsToOnDelete(required);\n\n let colDef = ` \"${colName}\" ${colType}`;\n if (required) colDef += \" NOT NULL\";\n columns.push(colDef);\n\n fkStatements.push(`ALTER TABLE \"${schema}\".\"${baseTableName}\" ADD CONSTRAINT \"${toPostgresIdentifier(`${baseTableName}_${colName}_fkey`)}\" FOREIGN KEY (\"${colName}\") REFERENCES \"${targetSchema}\".\"${targetTable}\" (\"${targetId}\") ON DELETE ${onDelete.toUpperCase()};`);\n }\n } else {\n const colName = resolveColumnName(propName, prop);\n\n // On an auth collection, the columns auth reads and writes\n // are defined once, in `auth-users-columns`, and every\n // creator of this table emits that definition. Before this,\n // the desired state here was built from the collection file\n // alone — which knows nothing of `is_anonymous` or\n // `tokens_valid_after` — so `db push` and the runtime\n // disagreed about `email`'s nullability and about which\n // columns exist at all.\n const authDefinition = isAuthCollection(collection)\n ? authUsersColumnDefinition(colName)\n : undefined;\n if (authDefinition && !isIdProperty(propName, prop, collection)) {\n columns.push(` \"${colName}\" ${authDefinition}`);\n return;\n }\n\n const colType = getSqlColumnType(propName, prop, collection, collections);\n let colDef = ` \"${colName}\" ${colType}`;\n\n if (isIdProperty(propName, prop, collection)) {\n colDef += \" PRIMARY KEY\";\n }\n\n if (\"isId\" in prop && prop.isId !== \"manual\" && prop.isId !== true && prop.isId !== \"increment\") {\n if (prop.isId === \"uuid\") {\n colDef += \" DEFAULT gen_random_uuid()\";\n } else if (prop.isId === \"cuid\") {\n colDef += \" DEFAULT cuid()\";\n } else if (typeof prop.isId === \"string\") {\n colDef += ` DEFAULT ${prop.isId}`;\n }\n }\n\n if (!isIdProperty(propName, prop, collection) && prop.validation?.unique) {\n colDef += \" UNIQUE\";\n }\n\n if (prop.type === \"date\") {\n const dateProp = prop as DateProperty;\n if (dateProp.autoValue === \"on_create\" || dateProp.autoValue === \"on_update\") {\n colDef += \" DEFAULT now()\";\n }\n }\n\n if (prop.validation?.required && !colDef.includes(\"PRIMARY KEY\")) {\n colDef += \" NOT NULL\";\n }\n\n columns.push(colDef);\n }\n });\n\n // ── Auth columns the collection file never mentions ──────────────\n // `db push` is declarative: Atlas diffs the database against exactly\n // what is emitted here and drops anything else. The scaffold's users\n // collection describes 12 columns while auth needs 14, so\n // `is_anonymous` and `tokens_valid_after` — created at boot by\n // `ensureAuthTablesExist` — read as unmanaged drift, and a push run\n // after the server had started once planned to DROP them. The\n // destructive gate catches it, which makes it a data-loss prompt on\n // the auth table rather than silent damage; either way the desired\n // state was wrong. Emit the full contract so the two agree.\n if (isAuthCollection(collection)) {\n const declared = new Set(\n Object.entries(collection.properties ?? {})\n .map(([name, prop]) => resolveColumnName(name, prop))\n );\n for (const spec of AUTH_USERS_COLUMNS) {\n if (declared.has(spec.column)) continue;\n columns.push(` \"${spec.column}\" ${authUsersColumnSql(spec)}`);\n }\n }\n\n // ── The opt-in search column ─────────────────────────────────────\n // Last, so it reads as what it is: derived from the columns above\n // it. Postgres recomputes it on every write of a source column and\n // rejects any attempt to write it directly, which is the property\n // that makes it impossible for the index to drift from the row.\n const searchSpec = options.includeSearch === false ? undefined : buildSearchColumnSpec(collection);\n if (searchSpec) {\n columns.push(` ${searchColumnDefinition(searchSpec)}`);\n const fuzzyDef = fuzzyColumnDefinition(searchSpec);\n if (fuzzyDef) columns.push(` ${fuzzyDef}`);\n indexStatements.push(...searchIndexStatements(searchSpec));\n }\n\n // ANN indexes for vector columns. Emitted with the other indexes\n // rather than inline, because `CREATE INDEX` is a statement and a\n // column definition is not — and because a column too wide for\n // pgvector to index still needs its column.\n //\n // Absent alongside their column when vector is carved out: an index\n // over a column this file no longer declares would not resolve, and\n // `USING hnsw` needs the extension the dev database cannot have.\n if (options.includeVector !== false) {\n const vectorPlan: VectorIndexPlan = buildVectorIndexPlan(collection, resolveColumnName);\n indexStatements.push(...vectorIndexStatements(vectorPlan));\n for (const skip of vectorPlan.skipped) {\n indexStatements.push(`-- No ANN index on \"${skip.schema}\".\"${skip.table}\".\"${skip.column}\": ${skip.reason}`);\n }\n }\n\n // The collection's own `indexes:` block. Last of the three index\n // producers, and the only one the developer wrote deliberately\n // rather than getting as a side effect of another feature.\n indexStatements.push(...collectionIndexStatements(\n buildCollectionIndexSpecs(collection, resolveColumnName)\n ));\n\n // The implicit primary key, for a collection that declares none.\n const hasPk = columns.some(c => c.includes(\"PRIMARY KEY\"));\n if (!hasPk) {\n columns.unshift(' \"id\" TEXT PRIMARY KEY');\n }\n\n ddl += columns.join(\",\\n\");\n ddl += `\\n);\\n\\n`;\n\n if (options.includePolicies) {\n // Enable RLS and add Policies. No FORCE: authenticated requests\n // run as the non-owner `rebase_user` role, which plain ENABLE\n // already binds. The owner (server context) must bypass — it is\n // the trusted plane (auth flows, dataAsAdmin).\n ddl += `ALTER TABLE \"${schema}\".\"${baseTableName}\" ENABLE ROW LEVEL SECURITY;\\n`;\n ddl += `\\n`;\n\n const securityRules = getEffectiveSecurityRules(collection);\n if (securityRules.length > 0) {\n const resolveCollection: ResolveCollection = (slug) => collections.find(c => c.slug === slug || getTableName(c) === slug);\n securityRules.forEach((rule: SecurityRule) => {\n policyStatements.push(generatePolicyDdl(collection, rule, resolveCollection));\n });\n }\n }\n }\n }\n\n // Indexes before foreign keys. Today every FK targets a primary key created\n // inline, so the order is safe by accident; the moment a declared\n // `unique: true` index can back an FK target, a constraint emitted first\n // would reference an index that does not exist yet.\n if (indexStatements.length > 0) {\n ddl += \"-- Indexes\\n\";\n ddl += indexStatements.join(\"\\n\") + \"\\n\\n\";\n }\n\n if (fkStatements.length > 0) {\n ddl += \"-- Foreign Key Constraints\\n\";\n ddl += fkStatements.join(\"\\n\") + \"\\n\\n\";\n }\n\n if (policyStatements.length > 0) {\n ddl += \"-- Row Level Security Policies\\n\";\n ddl += policyStatements.join(\"\");\n ddl += \"\\n\";\n }\n\n return ddl;\n};\n\n/** The RLS statements one declared collection's table needs, ready to run. */\n/**\n * A foreign key, as both its parts and the statement that creates it.\n *\n * `ALTER TABLE … ADD CONSTRAINT` has no `IF NOT EXISTS`, so a caller applying\n * these has to skip by name — hence the name is a field and not only a substring\n * of the SQL.\n */\nexport interface ForeignKeyPlan {\n constraintName: string;\n schema: string;\n /** Bare table name, no schema prefix. */\n table: string;\n column: string;\n targetSchema: string;\n targetTable: string;\n targetColumn: string;\n sql: string;\n}\n\n/** A column a `relation` or `reference` property owns on its own table. */\nexport interface RelationalColumnPlan {\n schema: string;\n /** Bare table name, no schema prefix. */\n table: string;\n column: string;\n /** Postgres type, exactly as the DDL generator declares it. */\n type: string;\n /** Absent when the target collection is not part of this bundle. */\n foreignKey?: ForeignKeyPlan;\n /**\n * What this column would have been called before `generateForeignKeyName`\n * learned to singularize — set only when the two differ and the column name\n * is the derived default rather than one the author wrote.\n *\n * Carried so the boot-time ensure can notice a database provisioned under\n * the old rule. It is never used to name anything.\n */\n legacyColumn?: string;\n}\n\n/** The table behind a many-to-many `through` relation. */\nexport interface JunctionTablePlan {\n schema: string;\n /** Bare table name, no schema prefix. */\n table: string;\n columns: { name: string; type: string; legacyName?: string }[];\n /** Both endpoint columns plus the composite primary key. */\n createTable: string;\n foreignKeys: ForeignKeyPlan[];\n}\n\nconst schemaOfCollection = (collection: CollectionConfig): string =>\n isPostgresCollectionConfig(collection) && collection.schema ? collection.schema : \"public\";\n\nconst bareTableName = (name: string): string => (name.includes(\".\") ? name.split(\".\").pop()! : name);\n\n/**\n * Truncate a derived identifier the way Postgres does: to 63 bytes, silently.\n *\n * This is not cosmetic, and not really a naming choice at all — it is agreeing\n * with the name the database ALREADY stored. `ADD CONSTRAINT` on a longer name\n * succeeds and records the truncated form, so the untruncated name this used to\n * derive matched nothing in the catalogue. Boot-ensure compares its planned\n * constraints against `readExistingSchema`, which reads catalogue names, so the\n * comparison could never hit: every boot re-issued `ADD CONSTRAINT` for the same\n * constraint, forever, and got \"already exists\" every time. Non-fatal (foreign\n * keys are the one action allowed to fail) and therefore permanent — an error in\n * the log on every restart of a project whose table and column names happened to\n * be long.\n *\n * Byte length, not string length: NAMEDATALEN is 64 bytes, and a multi-byte\n * character straddling the boundary would be cut mid-sequence by `slice(0, 63)`.\n */\n// Moved to `@rebasepro/utils` so the search-column builder derives index names\n// under the same 63-byte rule this file derives constraint names under. Two\n// copies of a truncation rule is two rules the moment one of them is edited.\n\nconst foreignKeyPlan = (\n args: Omit<ForeignKeyPlan, \"constraintName\" | \"sql\"> & { onDelete: string; onUpdate?: string }\n): ForeignKeyPlan => {\n const constraintName = toPostgresIdentifier(`${args.table}_${args.column}_fkey`);\n const onUpdate = args.onUpdate ? ` ON UPDATE ${args.onUpdate.toUpperCase()}` : \"\";\n return {\n constraintName,\n schema: args.schema,\n table: args.table,\n column: args.column,\n targetSchema: args.targetSchema,\n targetTable: args.targetTable,\n targetColumn: args.targetColumn,\n sql:\n `ALTER TABLE \"${args.schema}\".\"${args.table}\" ADD CONSTRAINT \"${constraintName}\" ` +\n `FOREIGN KEY (\"${args.column}\") REFERENCES \"${args.targetSchema}\".\"${args.targetTable}\" ` +\n `(\"${args.targetColumn}\") ON DELETE ${args.onDelete.toUpperCase()}${onUpdate};`\n };\n};\n\n/**\n * The FK columns the declared collections own — one entry per `relation`\n * (`belongsTo` side) or `reference` property.\n *\n * Split out of {@link generatePostgresDdl} so the boot-time schema ensure can\n * create the same columns with the same names, types and constraints. Before\n * this it skipped them outright, which was survivable only because `db push`\n * always followed; on a managed tenant nothing follows, so a table arrived\n * without the column its own collection reads and wrote 400 on every insert.\n *\n * A relation whose target is not in the bundle yields no column at all (the\n * generator returns early on an unresolvable target); a `reference` whose target\n * is unknown yields the column without a constraint. Both mirror the generator\n * exactly — a divergence here is a schema fork between boot and `db push`.\n */\nexport const planRelationalColumns = (allCollections: CollectionConfig[]): RelationalColumnPlan[] => {\n const collections = relationalCollections(allCollections);\n const plans: RelationalColumnPlan[] = [];\n\n for (const collection of collections) {\n const tableName = getTableName(collection);\n if (!tableName) continue;\n const schema = schemaOfCollection(collection);\n const table = bareTableName(tableName);\n\n for (const [propName, rawProp] of Object.entries(collection.properties ?? {})) {\n const prop = rawProp as Property;\n\n if (prop.type === \"relation\") {\n const refProp = prop as RelationProperty;\n const resolvedRelations = resolveCollectionRelations(collection);\n const relInfo = findRelation(resolvedRelations, refProp.relation?.relationName ?? propName);\n if (relInfo?.kind !== \"belongsTo\") continue;\n // The relation and an explicit FK property can both be declared;\n // the explicit one owns the column.\n if (collection.properties[relInfo.localKey] && propName !== relInfo.localKey) continue;\n\n let targetCollection: CollectionConfig;\n try {\n targetCollection = relInfo.target();\n } catch {\n continue;\n }\n if (!targetCollection) continue;\n\n const required = prop.validation?.required;\n // Only a *derived* column name can be affected by the change to\n // `generateForeignKeyName`; one the author wrote is theirs.\n const relationName = refProp.relation?.relationName ?? propName;\n const legacyKey = legacyForeignKeyName(relationName);\n const derived = relInfo.localKey === generateForeignKeyName(relationName);\n plans.push({\n schema,\n table,\n column: relInfo.localKey,\n legacyColumn: derived && legacyKey !== relInfo.localKey ? legacyKey : undefined,\n type: getSqlColumnType(propName, prop, collection, collections),\n foreignKey: foreignKeyPlan({\n schema,\n table,\n column: relInfo.localKey,\n targetSchema: schemaOfCollection(targetCollection),\n targetTable: bareTableName(getTableName(targetCollection)),\n targetColumn: getPrimaryKeyName(targetCollection),\n onDelete: relInfo.onDelete ?? defaultBelongsToOnDelete(required),\n onUpdate: relInfo.onUpdate\n })\n });\n } else if (prop.type === \"reference\") {\n const refProp = prop as ReferenceProperty;\n const targetCollection = collections.find(\n c => c.slug === refProp.path || getTableName(c) === refProp.path\n );\n const column = resolveColumnName(propName, prop);\n const type = getSqlColumnType(propName, prop, collection, collections);\n const required = prop.validation?.required;\n\n plans.push({\n schema,\n table,\n column,\n type,\n foreignKey: targetCollection\n ? foreignKeyPlan({\n schema,\n table,\n column,\n targetSchema: schemaOfCollection(targetCollection),\n targetTable: bareTableName(getTableName(targetCollection)),\n targetColumn: getPrimaryKeyName(targetCollection),\n // Same rule, same function as the `CREATE TABLE`\n // path above — a default that appears in one and not\n // the other makes `db push` plan a constraint\n // rewrite on every run.\n onDelete: defaultBelongsToOnDelete(required)\n })\n : undefined\n });\n }\n }\n }\n\n return plans;\n};\n\n/**\n * The junction tables a bundle's many-to-many relations imply.\n *\n * Derived from {@link resolveJunctionSpecs}, the same source the junction RLS\n * comes from, so a table created here always has policies planned for it — a\n * junction with row-level security left off is readable and writable by every\n * signed-in user, which is why the two must ship together.\n */\nexport const planJunctionTables = (allCollections: CollectionConfig[]): JunctionTablePlan[] => {\n const collections = relationalCollections(allCollections);\n const plans: JunctionTablePlan[] = [];\n\n for (const spec of resolveJunctionSpecs(collections).values()) {\n const [source, target] = spec.endpoints;\n // A junction column's default name is derived from the endpoint\n // collection's slug — which is normally plural, so this is where the\n // singularization change actually lands: a `categories` endpoint used\n // to give `categorie_id` and now gives `category_id`. Recorded, not\n // used, so boot-ensure can recognise a table built under the old rule.\n const legacyFor = (endpoint: typeof source): string | undefined => {\n const slug = toSnakeCase(endpoint.collection.slug ?? endpoint.collection.name ?? \"\");\n const legacy = legacyForeignKeyName(slug);\n const derived = endpoint.junctionColumn === generateForeignKeyName(slug);\n return derived && legacy !== endpoint.junctionColumn ? legacy : undefined;\n };\n const columns = [\n { name: source.junctionColumn, type: junctionKeyType(source.collection), legacyName: legacyFor(source) },\n { name: target.junctionColumn, type: junctionKeyType(target.collection), legacyName: legacyFor(target) }\n ];\n // Every declaring side agrees on the edge's lifetime; the first one wins,\n // as it does in the generator's walk.\n const onDelete = spec.declaringSides[0]?.relation.onDelete ?? \"CASCADE\";\n\n plans.push({\n schema: spec.schema,\n table: spec.table,\n columns,\n createTable:\n `CREATE TABLE IF NOT EXISTS \"${spec.schema}\".\"${spec.table}\" (` +\n columns.map(c => `\"${c.name}\" ${c.type} NOT NULL`).join(\", \") +\n `, PRIMARY KEY (${columns.map(c => `\"${c.name}\"`).join(\", \")}));`,\n foreignKeys: [source, target].map((endpoint, i) =>\n foreignKeyPlan({\n schema: spec.schema,\n table: spec.table,\n column: columns[i].name,\n targetSchema: schemaOfCollection(endpoint.collection),\n targetTable: bareTableName(getTableName(endpoint.collection)),\n targetColumn: getPrimaryKeyName(endpoint.collection),\n onDelete\n })\n )\n });\n }\n\n return plans;\n};\n\nexport interface CollectionPolicyPlan {\n /** The table's schema (e.g. `public`, `rebase`). */\n schema: string;\n /** The bare table name, no schema prefix. */\n table: string;\n /** `schema.table` — matches the keys `readExistingSchema` returns. */\n qualified: string;\n /** `ALTER TABLE … ENABLE ROW LEVEL SECURITY;` — locked by default. */\n enableRls: string;\n /** `DROP POLICY IF EXISTS` / `CREATE POLICY` statements, in order. */\n policyStatements: string[];\n}\n\n/**\n * The per-table RLS plan for the *declared* collections, as executable\n * statements — what the managed runtime applies at boot so a freshly\n * provisioned tenant database serves data instead of 401ing every read.\n *\n * Mirrors {@link generatePostgresPoliciesDdl} exactly (same\n * `generatePolicyStatements`, same enable-RLS, same effective rules, same\n * derived junction rules), so boot and `db push` produce identical policies from\n * identical collections.\n *\n * Junction tables are included, and have to be: boot creates them now\n * ({@link planJunctionTables}), and a junction with RLS left off is readable and\n * writable by every signed-in user. A junction whose table is still absent is\n * skipped by the applier, not planned away here.\n */\nexport const planCollectionPolicies = (allCollections: CollectionConfig[]): CollectionPolicyPlan[] => {\n // A store with no RLS gets no policies planned for it — `supportsRLS` is\n // false for exactly the engines `relationalCollections` filters out.\n const collections = relationalCollections(allCollections);\n const resolveCollection: ResolveCollection = (slug) => collections.find(c => c.slug === slug || getTableName(c) === slug);\n const plans: CollectionPolicyPlan[] = [];\n const seen = new Set<string>();\n\n for (const collection of collections) {\n const tableName = getTableName(collection);\n if (!tableName) continue;\n const schema = isPostgresCollectionConfig(collection) && collection.schema ? collection.schema : \"public\";\n const baseTableName = tableName.includes(\".\") ? tableName.split(\".\").pop()! : tableName;\n const qualified = `${schema}.${baseTableName}`;\n if (seen.has(qualified)) continue;\n seen.add(qualified);\n\n const policyStatements: string[] = [];\n for (const rule of getEffectiveSecurityRules(collection)) {\n policyStatements.push(...generatePolicyStatements(collection, rule, resolveCollection));\n }\n\n plans.push({\n schema,\n table: baseTableName,\n qualified,\n enableRls: `ALTER TABLE \"${schema}\".\"${baseTableName}\" ENABLE ROW LEVEL SECURITY;`,\n policyStatements\n });\n }\n\n // Junctions are derived from `through` relations rather than declared, so\n // the walk above never sees them.\n for (const spec of resolveJunctionSpecs(collections).values()) {\n const qualified = `${spec.schema}.${spec.table}`;\n if (seen.has(qualified)) continue;\n seen.add(qualified);\n\n const junctionCollection = getJunctionCollectionConfig(spec);\n const policyStatements: string[] = [];\n for (const rule of getJunctionSecurityRules(spec)) {\n policyStatements.push(...generatePolicyStatements(junctionCollection, rule, resolveCollection));\n }\n\n plans.push({\n schema: spec.schema,\n table: spec.table,\n qualified,\n enableRls: `ALTER TABLE \"${spec.schema}\".\"${spec.table}\" ENABLE ROW LEVEL SECURITY;`,\n policyStatements\n });\n }\n\n return plans;\n};\n\nexport const generatePostgresPoliciesDdl = (allCollections: CollectionConfig[]): string => {\n // Also the expectation `checkPolicyDrift` reconciles the database against,\n // so a non-SQL collection filtered out here is one the drift report cannot\n // invent a missing policy for.\n const collections = relationalCollections(allCollections);\n let ddl = \"-- This file contains RLS policies generated by Rebase. Applied separately from migrations.\\n\\n\";\n\n const allTablesToGenerate = new Map<string, {\n collection: CollectionConfig\n }>();\n\n for (const collection of collections) {\n const tableName = getTableName(collection);\n if (tableName) {\n allTablesToGenerate.set(tableName, { collection });\n }\n }\n\n for (const [tableName, { collection }] of allTablesToGenerate.entries()) {\n const schema = isPostgresCollectionConfig(collection) && collection.schema ? collection.schema : \"public\";\n const baseTableName = tableName.includes(\".\") ? tableName.split(\".\").pop()! : tableName;\n\n // No FORCE: user requests run as the non-owner `rebase_user` role\n // (plain ENABLE binds them); the owner is the trusted server context.\n ddl += `ALTER TABLE \"${schema}\".\"${baseTableName}\" ENABLE ROW LEVEL SECURITY;\\n`;\n ddl += `\\n`;\n\n const securityRules = getEffectiveSecurityRules(collection);\n if (securityRules.length > 0) {\n const resolveCollection: ResolveCollection = (slug) => collections.find(c => c.slug === slug || getTableName(c) === slug);\n const injectedNames = new Set(getInjectedSecurityRules(collection).map((rule) => rule.name));\n\n securityRules.forEach((rule: SecurityRule) => {\n // Say which policies the author did not write. They are permissive,\n // so they OR with the declared rules and widen the final ACL beyond\n // what `securityRules` reads like — and re-appear after any manual\n // DROP, because a push asserts the declared state.\n if (rule.name && injectedNames.has(rule.name)) {\n ddl += `-- Injected by Rebase (not from this collection's securityRules).\\n`;\n ddl += `-- Set \\`disableDefaultPolicies: true\\` on \"${collection.slug}\" to drop these and own its RLS outright.\\n`;\n }\n ddl += generatePolicyDdl(collection, rule, resolveCollection);\n });\n ddl += \"\\n\";\n }\n }\n\n // Junction tables are generated from `through` relations, not declared as\n // collections, so the walk above never sees them. They get the same\n // treatment as any generated table: locked by default, with derived\n // policies — reads follow the endpoints, writes follow the declaring\n // side's update rules.\n const junctionSpecs = resolveJunctionSpecs(collections);\n for (const spec of junctionSpecs.values()) {\n ddl += `ALTER TABLE \"${spec.schema}\".\"${spec.table}\" ENABLE ROW LEVEL SECURITY;\\n`;\n ddl += `\\n`;\n\n const junctionRules = getJunctionSecurityRules(spec);\n if (junctionRules.length === 0) continue;\n\n const junctionCollection = getJunctionCollectionConfig(spec);\n const resolveCollection: ResolveCollection = (slug) => collections.find(c => c.slug === slug || getTableName(c) === slug);\n const declaringSlugs = spec.declaringSides.map(s => s.collection.slug).join('\", \"');\n\n ddl += `-- Derived by Rebase for the junction \"${spec.table}\" (no collection declares it).\\n`;\n ddl += `-- Reads require both endpoint rows to be visible; writes follow the update\\n`;\n ddl += `-- rules of \"${declaringSlugs}\". Set \\`disableDefaultPolicies: true\\` on the\\n`;\n ddl += `-- declaring collection(s) to drop these and police the junction yourself.\\n`;\n junctionRules.forEach((rule: SecurityRule) => {\n ddl += generatePolicyDdl(junctionCollection, rule, resolveCollection);\n });\n ddl += \"\\n\";\n }\n\n return ddl;\n};\n\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"rls-enforcement-DeYEjYLt.js","names":[],"sources":["../src/security/rls-enforcement.ts"],"sourcesContent":["import { sql as drizzleSql, SQL } from \"drizzle-orm\";\nimport { ANONYMOUS_USER_ID, PolicyExpression, SecurityRule } from \"@rebasepro/types\";\nimport {\n AnonymousGrantRisk,\n findAnonymousGrants,\n REBASE_USER_ROLE,\n revokeInternalTableAccess,\n securityRuleToConditions\n} from \"@rebasepro/common\";\nimport { REBASE_SCHEMA, usesLegacyRlsFunctions } from \"@rebasepro/types\";\nimport { logger } from \"@rebasepro/server\";\n\n/**\n * Unified RLS enforcement — the \"user context vs server context\" model.\n *\n * Every operation runs in one of two contexts:\n *\n * - **User context** — a request authenticated (or anonymous) via\n * `driver.withAuth(user)`. Runs as the restricted `rebase_user` role: a\n * non-owner, NOSUPERUSER, NOBYPASSRLS role, so Postgres RLS binds *every*\n * statement (SELECT, INSERT, UPDATE, DELETE). The collection's\n * `securityRules` are the whole authorization model; app-layer callbacks\n * are validation/side-effects, not a security boundary.\n *\n * - **Server context** — the base (owner) connection: auth flows, migrations,\n * and raw `rebase.sql`. As table owner it bypasses RLS. This is the trusted\n * plane, equivalent to Supabase's `service_role`.\n *\n * `rebase.dataAsAdmin` is **not** in it, despite the name. `init.ts` scopes\n * that driver with `withAuth(SERVICE_IDENTITY)`, so it arrives as user\n * context above — `rebase_user`, `app.uid = 'service'`, policies evaluated —\n * and clears the default policies through their admin arm rather than the\n * `rebase.uid() IS NULL` one.\n *\n * This module provides the three pieces:\n *\n * 1. {@link detectConnectionPosture} — is the connection subject to RLS at\n * all? (superuser / BYPASSRLS / table owner ⇒ no)\n * 2. {@link ensureAppRole} — idempotently provision `rebase_user` with\n * SELECT/INSERT/UPDATE/DELETE grants (+ default privileges so future\n * tables stay covered).\n * 3. {@link applyAuthContext} — per-transaction: set the `app.*` GUCs the\n * policies read (`rebase.uid()` etc.) and `SET LOCAL ROLE rebase_user` so\n * RLS binds. Transaction-scoped, so it composes with poolers.\n *\n * Provisioning runs from the framework's own bootstrap/migrate (which already\n * self-creates the `auth` schema and functions) — enforcement is default-on,\n * not an operator opt-in.\n */\n\n/**\n * The restricted role every authenticated (user-context) request runs as.\n *\n * Re-exported, not re-declared: the same name is needed by\n * `@rebasepro/common`'s internal-table revokes, and two spellings of a role name\n * fail as a silent no-op rather than an error.\n */\nexport { REBASE_USER_ROLE };\n\n/** Minimal SQL runner so callers can adapt drizzle or pg.Client. */\nexport type RawSqlRunner = (sqlText: string) => Promise<Record<string, unknown>[]>;\n\n/** Minimal transaction surface needed by {@link applyAuthContext}. */\nexport interface SqlTx {\n execute(query: SQL): Promise<unknown>;\n}\n\nexport interface ConnectionPosture {\n /** The connection's `current_user`. */\n role: string;\n superuser: boolean;\n bypassRLS: boolean;\n /**\n * Owns at least one user table — owners bypass non-FORCE RLS.\n *\n * \"Owns\" in Postgres's sense, not by name: a role that INHERITs the owner is\n * the owner for every ownership test the planner makes, including the one\n * that decides whether a policy applies at all.\n */\n ownsTables: boolean;\n /**\n * May create tables somewhere the server writes — and would then own them.\n *\n * The reason this is part of the posture rather than a separate question:\n * the posture is decided ONCE, at boot, and on a fresh database the\n * catalogue has nothing to report yet. The process that asks then goes on to\n * create every table in the schema and owns all of them for the rest of its\n * life. Asking only what is already there answers about the wrong instant.\n */\n canCreateTables: boolean;\n /** True when RLS would NOT constrain this connection. */\n privileged: boolean;\n}\n\nexport interface AuthContext {\n uid: string;\n /** Raw roles as carried on the user (strings or `{ id }` objects). */\n roles: unknown[];\n /**\n * Whether this session came from anonymous sign-in rather than an account.\n *\n * Anonymous sign-in mints a real user row and a real uid, so a guest was\n * indistinguishable from a registered account inside a policy: same\n * `rebase.uid()`, same default role. Every rule meaning \"a signed-in\n * person\" was therefore also a rule about anybody who had called\n * `POST /auth/anonymous`, which asks for no email, no password and no\n * agreement to anything.\n *\n * Optional, and absent reads as `false`: a caller that predates this — a\n * realtime subscription, a custom validator — keeps the behaviour it had\n * rather than having every one of its users reclassified as guests.\n */\n isAnonymous?: boolean;\n}\n\nconst quoteIdent = (name: string): string => `\"${name.replace(/\"/g, \"\\\"\\\"\")}\"`;\n\n/** DML the user role holds on managed tables (RLS still filters per row). */\nconst USER_TABLE_PRIVILEGES = \"SELECT, INSERT, UPDATE, DELETE\";\n\n/**\n * Warn when the connection role shares its name with an existing schema.\n *\n * Postgres resolves unqualified names through `search_path`, which defaults to\n * `\"$user\", public` — and `$user` is the connection ROLE. When a schema of that\n * name exists it sits ahead of `public`, so every unqualified statement\n * silently operates on it instead:\n *\n * CREATE TABLE posts (...); -- you meant public.posts; you got <role>.posts\n *\n * Nothing errors. You get a second table of the same name in the wrong schema,\n * and reads that pin `public` cannot see it — which reads as \"missing table\" and\n * sends people to re-run a push that creates a *third* copy. The bootstrapper\n * has a whole branch dedicated to recognising the symptom after the fact.\n *\n * Rebase shipped straight into this: it creates a schema named `rebase` while\n * every template named the database role `rebase` too. The scaffold uses\n * `rebase_app` now, and every pool Rebase opens pins `search_path=public`\n * (`pinSearchPath`), which covers the paths the framework controls. This covers\n * the ones it does not — `psql`, `pg_dump`, drizzle-kit, a colleague's script,\n * a hand-written migration — because the hazard is a property of the two NAMES,\n * not of any one connection.\n *\n * A warning rather than a boot failure: the database works, the framework's own\n * traffic is pinned, and refusing to start over a naming choice a user may have\n * inherited would be worse than the risk.\n */\nexport async function warnOnRoleSchemaCollision(run: RawSqlRunner): Promise<void> {\n try {\n const rows = await run(`\n SELECT current_user AS role,\n EXISTS (\n SELECT 1 FROM pg_namespace n WHERE n.nspname = current_user\n ) AS collides\n `);\n if (rows[0]?.collides !== true) return;\n const role = String(rows[0]?.role ?? \"the connection role\");\n logger.warn(\n `⚠️ The database role \"${role}\" has the same name as a schema. Postgres resolves unqualified ` +\n `names through \\`search_path\\`, which defaults to \\`\"$user\", public\\` — so \"${role}\" is searched ` +\n `BEFORE public, and any unqualified \\`CREATE TABLE\\`/\\`SELECT\\` from a tool that does not pin the ` +\n `path (psql, pg_dump, drizzle-kit, a hand-written migration) silently lands in \"${role}\" instead. ` +\n `Rebase's own connections pin \\`search_path=public\\`, so the server is unaffected. To remove the ` +\n `hazard entirely, connect as a role whose name is not also a schema — the scaffold uses ` +\n `\"rebase_app\".`\n );\n } catch {\n // A diagnostic must never be the reason a boot fails.\n }\n}\n\n/**\n * Would RLS actually constrain this connection?\n *\n * Four ways the answer is no, and only two of them are obvious. Superuser and\n * BYPASSRLS announce themselves in `pg_roles`. The other two are about\n * ownership, and both used to read as \"no bypass here\":\n *\n * - **Ownership through membership.** The old query compared\n * `pg_tables.tableowner` to `current_user` as a string. Postgres does not: a\n * role that INHERITs the owner passes every ownership test the planner makes,\n * including the one that decides whether a policy applies. So a perfectly\n * ordinary \"app role that is a member of the owner role\" setup reported\n * unprivileged and bypassed every policy — permanently, since no restart\n * changes a string comparison's answer.\n *\n * - **Ownership that has not happened yet.** The posture is decided once, at\n * boot, and on a fresh database there is nothing in the catalogue to find. The\n * same process then creates every table and owns all of them. The first boot\n * of a new deployment therefore served every request unconstrained, the log\n * said RLS was fine, and the SECOND boot silently fixed it — which is the\n * worst possible shape for a security check, because the state that reproduces\n * it is gone by the time anyone looks. Asking \"may this role create tables\n * where the server provisions?\" answers about the process's whole life rather\n * than about one instant of it.\n *\n * `schemas` narrows the CREATE question to where the server actually writes.\n * Omitted, it asks about every schema the role can see, which is the safe\n * direction: over-reporting costs a role switch that was not strictly needed,\n * under-reporting costs the entire authorization model.\n */\nexport async function detectConnectionPosture(\n run: RawSqlRunner,\n schemas?: string[]\n): Promise<ConnectionPosture> {\n // Identifiers, and they reach SQL as literals in a catalogue comparison —\n // but they arrive from collection configs, so they are quoted as string\n // literals rather than interpolated as names, and a quote inside one is\n // escaped rather than trusted.\n const schemaFilter = schemas?.length\n ? `AND n.nspname IN (${Array.from(new Set(schemas))\n .map((s) => `'${s.replace(/'/g, \"''\")}'`)\n .join(\", \")})`\n : \"\";\n\n const rows = await run(`\n SELECT current_user AS role,\n r.rolsuper AS superuser,\n r.rolbypassrls AS bypassrls,\n EXISTS (\n SELECT 1 FROM pg_tables t\n WHERE pg_has_role(current_user, t.tableowner, 'USAGE')\n AND t.schemaname NOT IN ('pg_catalog', 'information_schema')\n ) AS owns_tables,\n EXISTS (\n SELECT 1 FROM pg_namespace n\n WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')\n AND n.nspname NOT LIKE 'pg\\\\_toast%'\n AND n.nspname NOT LIKE 'pg\\\\_temp%'\n ${schemaFilter}\n AND has_schema_privilege(current_user, n.oid, 'CREATE')\n ) AS can_create\n FROM pg_roles r\n WHERE r.rolname = current_user\n `);\n const row = rows[0] ?? {};\n const superuser = row.superuser === true;\n const bypassRLS = row.bypassrls === true;\n const ownsTables = row.owns_tables === true;\n const canCreateTables = row.can_create === true;\n return {\n role: String(row.role ?? \"unknown\"),\n superuser,\n bypassRLS,\n ownsTables,\n canCreateTables,\n privileged: superuser || bypassRLS || ownsTables || canCreateTables\n };\n}\n\n/**\n * Human-actionable instructions for when the connection cannot provision the\n * user role itself (no CREATEROLE and role not pre-created by the platform).\n */\nexport function appRoleSetupInstructions(connectionRole: string, schemas: string[]): string {\n const grants = schemas.map((s) =>\n `GRANT USAGE ON SCHEMA ${quoteIdent(s)} TO ${REBASE_USER_ROLE};\\n` +\n `GRANT ${USER_TABLE_PRIVILEGES} ON ALL TABLES IN SCHEMA ${quoteIdent(s)} TO ${REBASE_USER_ROLE};\\n` +\n `GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA ${quoteIdent(s)} TO ${REBASE_USER_ROLE};`\n ).join(\"\\n\");\n return (\n `Rebase enforces row-level security by running authenticated requests as ` +\n `the restricted role \"${REBASE_USER_ROLE}\", but the connection role ` +\n `\"${connectionRole}\" bypasses RLS and cannot create that role itself.\\n` +\n `Run the following as a database administrator, then restart:\\n\\n` +\n `CREATE ROLE ${REBASE_USER_ROLE} NOLOGIN NOSUPERUSER NOBYPASSRLS NOINHERIT;\\n` +\n `GRANT ${REBASE_USER_ROLE} TO ${quoteIdent(connectionRole)};\\n` +\n grants\n );\n}\n\n/**\n * Idempotently provision the `rebase_user` role, membership for the current\n * connection role, and DML grants (+ default privileges for future tables)\n * on every existing schema in `schemas`.\n *\n * Split into privilege tiers so it works both when the connection is a\n * superuser (creates everything) and when the platform pre-created the role\n * and membership (e.g. CNPG `postInitApplicationSQL`) and the connection is\n * merely the table owner — owners can always run the grant tier themselves.\n *\n * RLS still filters every row: these grants only make the tables *reachable*\n * by the role; the policies decide which rows/commands actually pass.\n *\n * Throws with precise setup instructions when the role is missing and the\n * connection cannot create it.\n */\nexport async function ensureAppRole(run: RawSqlRunner, schemas: string[]): Promise<void> {\n const uniqueSchemas = Array.from(new Set(schemas.filter(Boolean)));\n\n // Tier 1 — role existence.\n const roleRows = await run(`SELECT 1 FROM pg_roles WHERE rolname = '${REBASE_USER_ROLE}'`);\n if (roleRows.length === 0) {\n try {\n await run(`CREATE ROLE ${REBASE_USER_ROLE} NOLOGIN NOSUPERUSER NOBYPASSRLS NOINHERIT`);\n } catch (err) {\n throw new Error(\n `Failed to create the \"${REBASE_USER_ROLE}\" role: ${err instanceof Error ? err.message : String(err)}\\n\\n` +\n appRoleSetupInstructions(\"current connection role\", uniqueSchemas)\n );\n }\n }\n\n // Tier 2 — membership, so a non-superuser connection may SET ROLE to it.\n const memberRows = await run(`\n SELECT (pg_has_role(current_user, '${REBASE_USER_ROLE}', 'MEMBER')\n OR (SELECT rolsuper FROM pg_roles WHERE rolname = current_user)) AS can_set,\n current_user AS role\n `);\n if (memberRows[0]?.can_set !== true) {\n try {\n await run(`GRANT ${REBASE_USER_ROLE} TO CURRENT_USER`);\n } catch (err) {\n throw new Error(\n `The connection role is not a member of \"${REBASE_USER_ROLE}\" and cannot grant itself membership: ` +\n `${err instanceof Error ? err.message : String(err)}\\n\\n` +\n appRoleSetupInstructions(String(memberRows[0]?.role ?? \"current connection role\"), uniqueSchemas)\n );\n }\n }\n\n // Tier 3 — grants. Table owners (the expected non-superuser posture) can\n // always grant on their own objects, so this tier needs no extra privilege.\n const nspRows = await run(\"SELECT nspname FROM pg_namespace\");\n const existing = new Set(nspRows.map((r) => String(r.nspname)));\n for (const schema of uniqueSchemas) {\n if (!existing.has(schema)) continue;\n const s = quoteIdent(schema);\n await run(`GRANT USAGE ON SCHEMA ${s} TO ${REBASE_USER_ROLE}`);\n await run(`GRANT ${USER_TABLE_PRIVILEGES} ON ALL TABLES IN SCHEMA ${s} TO ${REBASE_USER_ROLE}`);\n await run(`GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA ${s} TO ${REBASE_USER_ROLE}`);\n // Cover objects created later by the CURRENT role (the role that runs\n // migrations), so a migrate can never strand the user role.\n await run(`ALTER DEFAULT PRIVILEGES IN SCHEMA ${s} GRANT ${USER_TABLE_PRIVILEGES} ON TABLES TO ${REBASE_USER_ROLE}`);\n await run(`ALTER DEFAULT PRIVILEGES IN SCHEMA ${s} GRANT USAGE, SELECT ON SEQUENCES TO ${REBASE_USER_ROLE}`);\n\n // The grants above are deliberately schema-wide — a project's own\n // collections may live in `rebase`, and future tables must be reachable\n // or a migration strands the role. Rebase's OWN tables are the exception:\n // refresh tokens, MFA secrets, API keys and the rest carry no RLS and no\n // row an end user should ever address. Taking the privilege back here\n // covers every table that already exists; each creator revokes on the\n // table it just made, for the boot that creates them for the first time.\n await revokeInternalTableAccess(async (text) => { await run(text); }, schema, {\n onError: (table, error) => logger.warn(\n `🔐 [rls] Could not revoke \"${REBASE_USER_ROLE}\" access to \"${schema}\".\"${table}\" — ` +\n \"it stays reachable by authenticated requests: \" +\n (error instanceof Error ? error.message : String(error))\n )\n });\n }\n\n logger.debug(`🔐 [rls] User role \"${REBASE_USER_ROLE}\" provisioned (schemas: ${uniqueSchemas.join(\", \")})`);\n}\n\n/**\n * Apply the authenticated context to a transaction: the `app.*` GUCs that RLS\n * policies read via `rebase.uid()` / `rebase.roles()` / `rebase.jwt()`, and — when\n * `userRole` is set — `SET LOCAL ROLE` so RLS binds every statement in this\n * transaction (reads *and* writes).\n *\n * GUCs are set with `is_local = true` and the role switch is `LOCAL`: both\n * reset at commit/rollback, so pooled connections are never polluted.\n *\n * Fails closed by construction: if the role switch errors, the transaction\n * aborts instead of proceeding privileged.\n *\n * SECURITY: this function is only ever called on the **user** path (the server\n * context uses the base/owner driver and never calls it). The default policies\n * treat `rebase.uid() IS NULL` as the trusted server context, and `rebase.uid()`\n * is `NULLIF(current_setting('app.uid'), '')` — so an EMPTY user id would\n * be read as NULL and silently escalate a user request to server privileges.\n * Coerce empty/blank ids to `ANONYMOUS_USER_ID` here, at the single chokepoint,\n * rather than trusting every caller (e.g. realtime subscription auth) to do it.\n * That sentinel is exported from `@rebasepro/types` because it leaks into rule\n * semantics: it is why `rebase.uid() IS NOT NULL` is true for anonymous requests.\n */\nexport async function applyAuthContext(tx: SqlTx, auth: AuthContext, userRole?: string): Promise<void> {\n const uid = typeof auth.uid === \"string\" && auth.uid.trim() !== \"\" ? auth.uid : ANONYMOUS_USER_ID;\n const normalizedRoles = auth.roles.map((r: unknown) =>\n typeof r === \"string\" ? r : (r as Record<string, unknown>)?.id ?? String(r)\n );\n // `app.user_id` is the pre-rename spelling, still written because policies\n // are data: a database provisioned before the rename holds rules compiled\n // to `current_setting('app.user_id')`, and those predicates would evaluate\n // to NULL — failing open or locking out — if we stopped setting it. Drop\n // the alias only once no live database carries a legacy policy.\n // A guest is a signed-in caller with no account behind them, and until this\n // the database could not tell the difference — see `rebase.is_anonymous()`.\n // Written as a string because that is what `set_config` takes.\n const isAnonymous = auth.isAnonymous === true ? \"true\" : \"false\";\n\n await tx.execute(drizzleSql`\n SELECT\n set_config('app.uid', ${uid}, true),\n set_config('app.user_id', ${uid}, true),\n set_config('app.user_roles', ${normalizedRoles.join(\",\")}, true),\n set_config('app.is_anonymous', ${isAnonymous}, true),\n set_config('app.jwt', ${JSON.stringify({\n sub: uid,\n roles: auth.roles,\n // In the claims too, so a policy reading `rebase.jwt()` — which is\n // how a rule reaches anything that is not uid or roles — sees the\n // same fact as `rebase.is_anonymous()`. Two sources that could\n // disagree would be worse than one.\n is_anonymous: auth.isAnonymous === true\n })}, true)\n `);\n if (userRole) {\n await tx.execute(drizzleSql.raw(`SET LOCAL ROLE ${quoteIdent(userRole)}`));\n }\n}\n\n/** Role names from other BaaS platforms that people reach for out of habit. */\nconst FOREIGN_CONVENTION_ROLES: Record<string, string> = {\n authenticated: \"Supabase\",\n anon: \"Supabase\",\n service_role: \"Supabase\"\n};\n\n/**\n * Warn about rules that read as \"signed-in users only\" but admit anonymous\n * callers — `rebase.uid() IS NOT NULL`, or a comparison against another\n * platform's magic user id such as `'anon'`.\n *\n * The sibling of {@link validatePolicyPgRoles}, for the more dangerous spelling\n * of the same habit. A foreign `pgRoles` value makes a policy unreachable and\n * the table reads empty — loud, and that guard throws. These do the opposite:\n * the rule compiles to a grant, and nothing looks wrong until the data is\n * already public.\n *\n * Warns rather than throws. Unlike an unreachable `pgRoles`, these rules are\n * serving traffic today: refusing to boot would take an app offline to report a\n * problem it already has, and on the read path it would take it offline\n * *because* its data was exposed. Rewriting the author's SQL is not an option\n * either — this is the escape hatch whose whole promise is that it means what it\n * says. So: say so, loudly, and leave the rule alone.\n */\nexport function warnOnAnonymousGrants(\n collections: { slug?: string; securityRules?: readonly SecurityRule[] }[]\n): void {\n // Grouped by the mistake, not by the rule: one habit typically repeats\n // across every collection an author wrote, and a per-rule list would repeat\n // the same paragraph dozens of times and get skimmed.\n const byRisk = new Map<string, { risk: AnonymousGrantRisk; sites: string[] }>();\n\n for (const collection of collections) {\n for (const rule of collection.securityRules ?? []) {\n const { usingExpr, withCheckExpr } = securityRuleToConditions(rule);\n const risks = [usingExpr, withCheckExpr]\n .filter((e): e is PolicyExpression => e !== null)\n .flatMap(findAnonymousGrants);\n\n for (const risk of risks) {\n const key = `${risk.pattern}:${risk.detail}`;\n const site = `${collection.slug ?? \"(unnamed)\"} → \"${rule.name ?? \"(unnamed rule)\"}\"`;\n const entry = byRisk.get(key) ?? { risk, sites: [] };\n if (!entry.sites.includes(site)) entry.sites.push(site);\n byRisk.set(key, entry);\n }\n }\n }\n\n if (byRisk.size === 0) return;\n\n const problems = [...byRisk.values()].map(({ risk, sites }) =>\n ` • ${risk.explanation}\\n ${sites.length} rule(s): ${sites.join(\", \")}`\n );\n\n logger.warn(\n `Security rules that read as a lockdown but grant access to anonymous requests. Every caller from a ` +\n `client carries a user id ('${ANONYMOUS_USER_ID}' when nobody is signed in), so these clauses are ` +\n `true for everyone:\\n\\n` +\n problems.join(\"\\n\\n\") + \"\\n\"\n );\n}\n\n/**\n * Name the collections whose raw policy SQL still calls the pre-1.0 helpers.\n *\n * The compiler rewrites `auth.uid()` to `rebase.uid()` on the way into the\n * database, so nothing is broken and no policy is wrong — which is exactly why\n * this has to be said out loud. A silent rewrite that works forever is not a\n * migration, it is a second supported spelling nobody wrote down, and the next\n * person to read those rules will copy the old one.\n *\n * Only `raw` expressions can carry it. Structured rules (`policy.authUid()`,\n * `policy.rolesOverlap(...)`) compile from the model and were never affected.\n */\nexport function warnOnLegacyRlsFunctions(\n collections: { slug?: string; securityRules?: readonly SecurityRule[] }[]\n): void {\n const sites: string[] = [];\n\n for (const collection of collections) {\n for (const rule of collection.securityRules ?? []) {\n const { usingExpr, withCheckExpr } = securityRuleToConditions(rule);\n const carriesLegacy = [usingExpr, withCheckExpr]\n .filter((e): e is PolicyExpression => e !== null)\n .some(containsLegacyRlsCall);\n if (!carriesLegacy) continue;\n\n const site = `${collection.slug ?? \"(unnamed)\"} → \"${rule.name ?? \"(unnamed rule)\"}\"`;\n if (!sites.includes(site)) sites.push(site);\n }\n }\n\n if (sites.length === 0) return;\n\n logger.warn(\n `These security rules call the pre-1.0 RLS helpers (\\`auth.uid()\\`, \\`auth.roles()\\`, \\`auth.jwt()\\`). ` +\n `They still work — the compiler rewrites them — but the functions now live in the \\`rebase\\` schema, ` +\n `and the \\`auth\\` one is Supabase's. Update the raw SQL in these rules to \\`${REBASE_SCHEMA}.uid()\\` ` +\n `and friends, or switch them to the structured helpers (\\`policy.authUid()\\`, \\`policy.rolesOverlap()\\`), ` +\n `which never had to be spelled by hand:\\n\\n` +\n sites.map(s => ` • ${s}`).join(\"\\n\") + \"\\n\"\n );\n}\n\n/** Whether any `raw` expression in the tree calls a pre-1.0 helper. */\nfunction containsLegacyRlsCall(expr: PolicyExpression): boolean {\n switch (expr.kind) {\n case \"raw\":\n return usesLegacyRlsFunctions(expr.sql);\n case \"and\":\n case \"or\":\n return expr.operands.some(containsLegacyRlsCall);\n case \"not\":\n return containsLegacyRlsCall(expr.operand);\n case \"existsIn\":\n return containsLegacyRlsCall(expr.where);\n default:\n return false;\n }\n}\n\n/**\n * Reject `pgRoles` that this server can never satisfy.\n *\n * `pgRoles` sets the `TO` clause of a generated policy, so a policy naming a\n * role the request never runs as simply never applies — and RLS then filters\n * every row. The table reads as empty, which is indistinguishable from having\n * no data, so the mistake survives review and ships.\n *\n * Requests run as `rebase_user`, so a policy is only reachable if it targets\n * `public` or a role `rebase_user` holds. Anything else is a configuration\n * error worth failing the boot for.\n */\nexport async function validatePolicyPgRoles(\n run: RawSqlRunner,\n collections: { slug?: string; securityRules?: readonly { name?: string; pgRoles?: readonly string[] }[] }[],\n /** The role requests actually run as: `rebase_user` when the connection is\n * privileged enough to switch, otherwise the connection role itself. */\n requestRole: string = REBASE_USER_ROLE\n): Promise<void> {\n const wanted = new Map<string, string[]>();\n for (const collection of collections) {\n for (const rule of collection.securityRules ?? []) {\n for (const role of rule.pgRoles ?? []) {\n if (role === \"public\") continue;\n wanted.set(role, [...(wanted.get(role) ?? []), collection.slug ?? \"(unnamed)\"]);\n }\n }\n }\n if (wanted.size === 0) return;\n\n const names = [...wanted.keys()].map((r) => `'${r.replace(/'/g, \"''\")}'`).join(\",\");\n const escapedRequestRole = requestRole.replace(/'/g, \"''\");\n const rows = await run(`\n SELECT r.rolname AS role,\n COALESCE(pg_has_role(to_regrole('${escapedRequestRole}'), r.oid, 'MEMBER'), false) AS reachable\n FROM pg_roles r\n WHERE r.rolname IN (${names})\n `);\n\n const reachable = new Map(rows.map((row) => [String(row.role), row.reachable === true]));\n const problems: string[] = [];\n\n for (const [role, slugs] of wanted) {\n if (reachable.get(role) === true) continue;\n\n const why = reachable.has(role)\n ? `\"${requestRole}\" is not a member of it`\n : \"no such role exists in this database\";\n const platform = FOREIGN_CONVENTION_ROLES[role];\n const hint = platform\n ? `\"${role}\" is a ${platform} convention, not a PostgreSQL role. Application roles belong in \\`roles: [\"${role === \"service_role\" ? \"admin\" : role}\"]\\`, which is checked inside the policy via rebase.roles().`\n : `Either grant it (GRANT ${role} TO ${requestRole}) or drop \\`pgRoles\\` so the policy targets \\`public\\`.`;\n\n problems.push(\n ` • pgRoles: [\"${role}\"] on ${slugs.join(\", \")} — ${why}.\\n ${hint}`\n );\n }\n\n if (problems.length > 0) {\n throw new Error(\n `Security rules target PostgreSQL roles this server cannot use. Requests run as ` +\n `\"${requestRole}\", so these policies would never apply and every row would be ` +\n `filtered out — the collections would look empty rather than error.\\n\\n` +\n problems.join(\"\\n\\n\") + \"\\n\"\n );\n }\n}\n"],"mappings":"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"}
|