@rebasepro/server-postgres 0.25.0 → 0.26.0
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-iXXZeVYD.js → BranchService-CWBnDhcv.js} +88 -12
- package/dist/BranchService-CWBnDhcv.js.map +1 -0
- package/dist/PostgresBackendDriver.d.ts +13 -0
- package/dist/PostgresBootstrapper.d.ts +7 -0
- package/dist/auth/services.d.ts +8 -1
- package/dist/auth/verify-auth-tables.d.ts +21 -0
- package/dist/backup/backup-cli.d.ts +40 -3
- package/dist/backup/backup-service.d.ts +11 -0
- package/dist/{backup-service-Bb39plam.js → backup-cli-PoRG-zEq.js} +777 -10
- package/dist/backup-cli-PoRG-zEq.js.map +1 -0
- package/dist/{boot-ddl-s_3Hkz94.js → boot-ddl-EsgqDkx0.js} +3 -3
- package/dist/{boot-ddl-s_3Hkz94.js.map → boot-ddl-EsgqDkx0.js.map} +1 -1
- package/dist/branch-argv.d.ts +15 -0
- package/dist/branch-diff-CxE4VlxT.js +1396 -0
- package/dist/branch-diff-CxE4VlxT.js.map +1 -0
- package/dist/branch-diff-report-DpvVf7hR.js +48 -0
- package/dist/branch-diff-report-DpvVf7hR.js.map +1 -0
- package/dist/branching/branch-diff-report.d.ts +7 -0
- package/dist/branching/branch-diff.d.ts +115 -0
- package/dist/{cli-console-DQ_K-sdM.js → cli-console-f0k6_TsJ.js} +41 -9
- package/dist/cli-console-f0k6_TsJ.js.map +1 -0
- package/dist/cli-console.d.ts +17 -1
- package/dist/{cli-errors-DwQnNATs.js → cli-errors-DsYyNYzd.js} +90 -11
- package/dist/cli-errors-DsYyNYzd.js.map +1 -0
- package/dist/cli-errors.d.ts +14 -0
- package/dist/cli-flags.d.ts +51 -0
- package/dist/{cli-prompt-egtNKkZl.js → cli-prompt-DXG0yk-h.js} +39 -4
- package/dist/{cli-prompt-egtNKkZl.js.map → cli-prompt-DXG0yk-h.js.map} +1 -1
- package/dist/cli.js +121 -271
- package/dist/cli.js.map +1 -1
- package/dist/{collections-schema-version-T6cGvrDi.js → collections-schema-version-D7bDI0PJ.js} +13 -9
- package/dist/collections-schema-version-D7bDI0PJ.js.map +1 -0
- package/dist/{column-plan-helpers-D_V4oaZh.js → column-plan-helpers-IG6uFb1s.js} +2 -2
- package/dist/{column-plan-helpers-D_V4oaZh.js.map → column-plan-helpers-IG6uFb1s.js.map} +1 -1
- package/dist/{connection-C938nR3E.js → connection-czvI7TZL.js} +57 -10
- package/dist/connection-czvI7TZL.js.map +1 -0
- package/dist/{databasePoolManager-Yqdw14wY.js → databasePoolManager-3stMNFL-.js} +4 -6
- package/dist/databasePoolManager-3stMNFL-.js.map +1 -0
- package/dist/db-command-line.d.ts +14 -0
- package/dist/db-subcommands.d.ts +11 -0
- package/dist/{deferred-index-build-Cis_HoGn.js → deferred-index-build-CqaOv1Xx.js} +8 -8
- package/dist/{deferred-index-build-Cis_HoGn.js.map → deferred-index-build-CqaOv1Xx.js.map} +1 -1
- package/dist/{doctor-DvJH-IQG.js → doctor-DW_mlTzz.js} +186 -42
- package/dist/doctor-DW_mlTzz.js.map +1 -0
- package/dist/{ensure-collection-policies-CCqayoUz.js → ensure-collection-policies-DLWgabQ2.js} +13 -10
- package/dist/ensure-collection-policies-DLWgabQ2.js.map +1 -0
- package/dist/{ensure-collection-tables-DvvImXDe.js → ensure-collection-tables-CFIRq8ni.js} +9 -9
- package/dist/{ensure-collection-tables-DvvImXDe.js.map → ensure-collection-tables-CFIRq8ni.js.map} +1 -1
- package/dist/{ensure-tables-Dpgwx5sy.js → ensure-tables-4nA-tfwi.js} +5 -5
- package/dist/{ensure-tables-Dpgwx5sy.js.map → ensure-tables-4nA-tfwi.js.map} +1 -1
- package/dist/{enum-labels-kept-CUlUlRkO.js → enum-labels-kept-PcPW65xy.js} +3 -3
- package/dist/{enum-labels-kept-CUlUlRkO.js.map → enum-labels-kept-PcPW65xy.js.map} +1 -1
- package/dist/{generate-drizzle-schema-C3EAP9HE.js → generate-drizzle-schema-DC0BOUpn.js} +3 -3
- package/dist/{generate-drizzle-schema-C3EAP9HE.js.map → generate-drizzle-schema-DC0BOUpn.js.map} +1 -1
- package/dist/{generate-drizzle-schema-logic-DA3zM8zc.js → generate-drizzle-schema-logic-CPzCRD-8.js} +4 -4
- package/dist/{generate-drizzle-schema-logic-DA3zM8zc.js.map → generate-drizzle-schema-logic-CPzCRD-8.js.map} +1 -1
- package/dist/{generate-files-ClmOUHXj.js → generate-files-BP-4W_gm.js} +6 -6
- package/dist/{generate-files-ClmOUHXj.js.map → generate-files-BP-4W_gm.js.map} +1 -1
- package/dist/{generate-next-step-DCeS4LrJ.js → generate-next-step-DgK5BlBn.js} +2 -2
- package/dist/{generate-next-step-DCeS4LrJ.js.map → generate-next-step-DgK5BlBn.js.map} +1 -1
- package/dist/{generate-postgres-ddl-logic-4eUP4t8w.js → generate-postgres-ddl-logic-DPl1oH_z.js} +4 -4
- package/dist/{generate-postgres-ddl-logic-4eUP4t8w.js.map → generate-postgres-ddl-logic-DPl1oH_z.js.map} +1 -1
- package/dist/generated-schema-diff-eZCklRZQ.js +1025 -0
- package/dist/generated-schema-diff-eZCklRZQ.js.map +1 -0
- package/dist/{generated-schema-staleness-FlxM2i4i.js → generated-schema-staleness-BvNF3i-s.js} +2 -2
- package/dist/{generated-schema-staleness-FlxM2i4i.js.map → generated-schema-staleness-BvNF3i-s.js.map} +1 -1
- package/dist/history/HistoryService.d.ts +7 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.es.js +288 -11
- package/dist/index.es.js.map +1 -1
- package/dist/{introspect-db-logic-CI5CU_n0.js → introspect-db-logic-DgQVzccW.js} +2 -2
- package/dist/{introspect-db-logic-CI5CU_n0.js.map → introspect-db-logic-DgQVzccW.js.map} +1 -1
- package/dist/{rls-bootstrap-sql-Bd-hJk08.js → migrate-baseline-B-2Ef4Mv.js} +718 -41
- package/dist/migrate-baseline-B-2Ef4Mv.js.map +1 -0
- package/dist/migrate-policy-stamps-BQtFHApx.js +89 -0
- package/dist/migrate-policy-stamps-BQtFHApx.js.map +1 -0
- package/dist/{open-rebase-BKA7Nyoe.js → open-rebase-C8jQ6s5a.js} +324 -1047
- package/dist/open-rebase-C8jQ6s5a.js.map +1 -0
- package/dist/open-rebase.d.ts +9 -1
- package/dist/{pg-error-utils-Bv5n2PLn.js → pg-error-utils-DhiUTlVx.js} +2 -2
- package/dist/{pg-error-utils-Bv5n2PLn.js.map → pg-error-utils-DhiUTlVx.js.map} +1 -1
- package/dist/{plan-schema-CXOucCw-.js → plan-schema-Czf3q4-Q.js} +4 -4
- package/dist/{plan-schema-CXOucCw-.js.map → plan-schema-Czf3q4-Q.js.map} +1 -1
- package/dist/{policy-drift-vm0BcvcE.js → policy-drift-X4A-xAzv.js} +3 -3
- package/dist/{policy-drift-vm0BcvcE.js.map → policy-drift-X4A-xAzv.js.map} +1 -1
- package/dist/pool-disconnects.d.ts +28 -0
- package/dist/{push-index-prebuild-DIequ4bV.js → push-index-prebuild-UTvB5pqH.js} +4 -4
- package/dist/{push-index-prebuild-DIequ4bV.js.map → push-index-prebuild-UTvB5pqH.js.map} +1 -1
- package/dist/push-kept-defaults-BGOlOqRQ.js +137 -0
- package/dist/push-kept-defaults-BGOlOqRQ.js.map +1 -0
- package/dist/{push-legacy-renames-BqJut4F3.js → push-legacy-renames-BwaKi8xF.js} +4 -4
- package/dist/{push-legacy-renames-BqJut4F3.js.map → push-legacy-renames-BwaKi8xF.js.map} +1 -1
- package/dist/{push-policy-plan-CvX8hiPb.js → push-policy-plan-DQ2tWceD.js} +5 -5
- package/dist/{push-policy-plan-CvX8hiPb.js.map → push-policy-plan-DQ2tWceD.js.map} +1 -1
- package/dist/{render-ddl-CQNwZdse.js → render-ddl-4v88y-Gv.js} +4 -4
- package/dist/{render-ddl-CQNwZdse.js.map → render-ddl-4v88y-Gv.js.map} +1 -1
- package/dist/{rls-enforcement-D7P4-ZYX.js → rls-enforcement-BOHuHVlc.js} +43 -4
- package/dist/rls-enforcement-BOHuHVlc.js.map +1 -0
- package/dist/schema/destructive-sql.d.ts +16 -0
- package/dist/schema/doctor-cli.js +5 -5
- package/dist/schema/doctor.d.ts +20 -2
- package/dist/schema/ensure-collection-policies.d.ts +4 -1
- package/dist/schema/generate-drizzle-schema.js +1 -1
- package/dist/schema/generate-postgres-ddl.js +2 -2
- package/dist/schema/generated-schema-diff.d.ts +21 -0
- package/dist/schema/introspect-db.js +5 -6
- package/dist/schema/introspect-db.js.map +1 -1
- package/dist/schema/migrate-policy-stamps.d.ts +23 -0
- package/dist/schema/migration-freshness.d.ts +69 -0
- package/dist/schema/push-kept-defaults.d.ts +57 -0
- package/dist/schema/push-policy-plan.d.ts +5 -0
- package/dist/schema/rls-bootstrap-sql.d.ts +47 -10
- package/dist/security/rls-enforcement.d.ts +18 -0
- package/dist/services/BranchService.d.ts +20 -2
- package/dist/services/FetchService.d.ts +12 -0
- package/dist/services/channel-presence.d.ts +3 -1
- package/dist/services/nested-path.d.ts +16 -0
- package/dist/services/pg-notify-listener.d.ts +2 -0
- package/dist/services/realtimeService.d.ts +18 -1
- package/dist/{source-Bv5KPeIx.js → source-fZb92pN8.js} +2 -2
- package/dist/{source-Bv5KPeIx.js.map → source-fZb92pN8.js.map} +1 -1
- package/dist/{vector-index-BFhyEaiz.js → vector-index-DsQ3V2d2.js} +2 -2
- package/dist/{vector-index-BFhyEaiz.js.map → vector-index-DsQ3V2d2.js.map} +1 -1
- package/dist/{websocket-MC0zTBar.js → websocket-CKYc3V4N.js} +56 -14
- package/dist/websocket-CKYc3V4N.js.map +1 -0
- package/package.json +7 -7
- package/dist/BranchService-iXXZeVYD.js.map +0 -1
- package/dist/backup-cli-B2CAn9fR.js +0 -647
- package/dist/backup-cli-B2CAn9fR.js.map +0 -1
- package/dist/backup-service-Bb39plam.js.map +0 -1
- package/dist/cli-console-DQ_K-sdM.js.map +0 -1
- package/dist/cli-errors-DwQnNATs.js.map +0 -1
- package/dist/cli-flags-D-b0qL9T.js +0 -206
- package/dist/cli-flags-D-b0qL9T.js.map +0 -1
- package/dist/collections-schema-version-T6cGvrDi.js.map +0 -1
- package/dist/connection-C938nR3E.js.map +0 -1
- package/dist/databasePoolManager-Yqdw14wY.js.map +0 -1
- package/dist/doctor-DvJH-IQG.js.map +0 -1
- package/dist/ensure-collection-policies-CCqayoUz.js.map +0 -1
- package/dist/open-rebase-BKA7Nyoe.js.map +0 -1
- package/dist/rls-bootstrap-sql-Bd-hJk08.js.map +0 -1
- package/dist/rls-enforcement-D7P4-ZYX.js.map +0 -1
- package/dist/rolldown-runtime-V7rFbMRe.js +0 -39
- package/dist/websocket-MC0zTBar.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deferred-index-build-Cis_HoGn.js","names":[],"sources":["../src/schema/column-type-drift.ts","../src/schema/plan/diff-plan.ts","../src/schema/deferred-index-build.ts"],"sourcesContent":["/**\n * A column whose type in the database is not the type the collection declares.\n *\n * The boot ensure is additive: it creates tables, columns, enum values and\n * indexes, and it never changes an existing column's type. That is the right\n * default — an automatic `ALTER COLUMN … TYPE` against customer data, decided\n * by a process nobody is watching, is not a thing to do quietly.\n *\n * What was wrong is that it happened *silently*. A property's `columnType`\n * changed after the first deploy, the deploy reported success, the column stayed\n * as it was, and the divergence surfaced later as writes failing:\n *\n * Invalid data format in \"listing_observations\":\n * malformed array literal: \"[0,0,0.0755,…]\"\n *\n * ...after a 10,690-row import, from a deployment that had said \"success\". The\n * two ends disagreed about the schema, so no value satisfied both: the declared\n * shape was rejected by Postgres and the Postgres shape was rejected by the\n * API's own validator, first. There is no encoding out of that, only a report.\n *\n * ## Why the comparison is deliberately blunt\n *\n * A drift report that cries wolf is worse than none, because the boot that says\n * it every time is the boot nobody reads. So types are compared by *family*, and\n * a family is only split from another when the difference changes what the\n * column can hold:\n *\n * - every numeric type is one family. `int4`, `numeric` and `float8` all arrive\n * as JavaScript numbers and accept each other's values; a database adopted\n * from an existing schema legitimately has `int4` where `type: \"number\"`\n * generates `NUMERIC`, and that is not news.\n * - every character type is one family, for the same reason: `text`,\n * `varchar(255)` and `char(3)` differ in what they *reject*, not in shape.\n * - `jsonb` and an array are different families, and that is the case this\n * exists for.\n *\n * Anything this module does not recognise — an enum type, a domain, a PostGIS\n * geometry, an extension type it has never met — returns `null` and is compared\n * with nothing. Silence on an unknown type is the deliberate choice: the cost of\n * a missed report is a question somebody asks once, and the cost of a false one\n * is a warning everybody learns to skip.\n */\n\n/** A column whose live type and declared type are in different families. */\nexport interface ColumnTypeDrift {\n /** `schema.table`. */\n table: string;\n /** The column name as it exists in the database. */\n column: string;\n /** What the collection says the column should be, e.g. `JSONB`. */\n declared: string;\n /** What the database actually has, as `udt_name` reports it, e.g. `_numeric`. */\n actual: string;\n}\n\n/**\n * Types that mean the same thing to everything above the driver.\n *\n * Keyed by every spelling either side can produce: `information_schema` reports\n * `udt_name` (`int4`, `_numeric`, `bpchar`), while the generators emit SQL\n * (`INTEGER`, `NUMERIC[]`, `CHAR(3)`). Both are normalised through here.\n */\nconst FAMILIES: Record<string, string> = {\n // Numbers. One family on purpose — see the note above.\n int2: \"number\", smallint: \"number\",\n int4: \"number\", int: \"number\", integer: \"number\",\n int8: \"number\", bigint: \"number\",\n numeric: \"number\", decimal: \"number\",\n float4: \"number\", real: \"number\",\n float8: \"number\", \"double precision\": \"number\",\n money: \"number\",\n\n // Characters. Also one family: the differences are limits, not shapes.\n text: \"text\", varchar: \"text\", \"character varying\": \"text\",\n bpchar: \"text\", char: \"text\", character: \"text\", citext: \"text\",\n\n bool: \"boolean\", boolean: \"boolean\",\n\n json: \"json\",\n jsonb: \"jsonb\",\n\n uuid: \"uuid\",\n bytea: \"bytea\",\n\n date: \"date\",\n time: \"time\", \"time without time zone\": \"time\",\n timetz: \"time\", \"time with time zone\": \"time\",\n timestamp: \"timestamp\", \"timestamp without time zone\": \"timestamp\",\n timestamptz: \"timestamptz\", \"timestamp with time zone\": \"timestamptz\",\n interval: \"interval\",\n\n tsvector: \"tsvector\",\n vector: \"vector\",\n halfvec: \"halfvec\",\n sparsevec: \"sparsevec\"\n};\n\n/**\n * One SQL or `udt_name` spelling reduced to its family, or `null` when this\n * module has no opinion about it.\n *\n * `null` is not a failure. It is the answer for an enum type, a domain, a\n * PostGIS column, a quoted qualified name — everything whose identity is a\n * *name* rather than a shape, where two spellings being different says nothing\n * about whether the values are compatible.\n */\nexport function typeFamily(raw: string | undefined | null): string | null {\n if (!raw) return null;\n let type = raw.trim().toLowerCase();\n if (!type) return null;\n\n // `\"public\".\"posts_status\"` — an enum, identified by name. Not comparable.\n if (type.startsWith(\"\\\"\")) return null;\n\n // `INTEGER GENERATED BY DEFAULT AS IDENTITY` is an `int4` with a default\n // attached; the identity clause is not part of the type.\n const generated = type.indexOf(\" generated \");\n if (generated > 0) type = type.slice(0, generated).trim();\n\n // Arrays, in both spellings: `NUMERIC[]` from the generators, `_numeric`\n // from `udt_name`. Compared element-wise, so `text[]` and `numeric[]` are\n // different and `int4[]` and `numeric[]` are not.\n if (type.endsWith(\"[]\")) {\n const element = typeFamily(type.slice(0, -2));\n return element && `array:${element}`;\n }\n if (type.startsWith(\"_\")) {\n const element = typeFamily(type.slice(1));\n return element && `array:${element}`;\n }\n\n // `VARCHAR(255)`, `VECTOR(384)`, `NUMERIC(10, 2)` — the width is a limit on\n // a family, not a family. A vector whose *dimensions* changed is a separate\n // concern with its own handling in the ensure.\n const paren = type.indexOf(\"(\");\n if (paren > 0) type = type.slice(0, paren).trim();\n\n return FAMILIES[type] ?? null;\n}\n\n/**\n * Do these two spellings describe columns that hold the same kind of value?\n *\n * `true` whenever the answer is not confidently \"no\" — an unrecognised type on\n * either side agrees with everything, because a report needs evidence and an\n * unknown type is the absence of it.\n */\nexport function typesAgree(declared: string, actual: string): boolean {\n const a = typeFamily(declared);\n const b = typeFamily(actual);\n if (a === null || b === null) return true;\n if (a !== b) return false;\n // Same family, and for `numeric` that is not the end of it: the modifier is\n // what makes `NUMERIC(10, 2)` a price and a bare `NUMERIC` a number that\n // keeps whatever the caller sent. A property that declares\n // `precision`/`scale` is asking the database to round, and a column without\n // them silently does not — so the two disagree, and the drift report says\n // so. A declaration with no modifier accepts whatever is there, which is\n // what it always did.\n //\n // Tested on the spelling, not the family: the family is `number` for every\n // numeric type, and an `int4` under a `NUMERIC(10, 2)` stays the adopted\n // schema it was.\n if (!isNumeric(declared) || !isNumeric(actual)) return true;\n const declaredModifier = numericModifier(declared);\n if (declaredModifier === null) return true;\n return declaredModifier === numericModifier(actual);\n}\n\n/** `NUMERIC`, `numeric(10,2)`, `DECIMAL(10, 2)` — the arbitrary-precision type itself. */\nfunction isNumeric(raw: string): boolean {\n return /^\\s*(?:numeric|decimal)\\s*(?:\\(|$)/i.test(raw);\n}\n\n/**\n * `numeric(10,2)` / `NUMERIC(10, 2)` → `10,2`; a bare `numeric` → `null`. A\n * precision alone means a scale of 0, which is how the catalogue reports it:\n * `NUMERIC(10)` → `10,0`.\n */\nfunction numericModifier(raw: string): string | null {\n const match = raw.match(/\\(([^)]*)\\)/);\n if (!match) return null;\n const [precision, scale = \"0\"] = match[1].split(\",\").map(part => part.trim());\n return `${precision},${scale}`;\n}\n\n/**\n * The sentence a boot log says about one drifted column.\n *\n * Written to be actionable in the place it is read, which is a runtime log with\n * no way to run a migration from it: it names both types, says which surface\n * will break first, and gives the statement that resolves it.\n */\nexport function columnTypeDriftMessage(drift: ColumnTypeDrift): string {\n const [schema, table] = drift.table.split(\".\");\n return (\n `Column \"${drift.table}\".\"${drift.column}\" is ${drift.actual} in the database, but this ` +\n `collection declares ${drift.declared}. Boot does not change an existing column's type, so ` +\n \"the database keeps what it has and writes that match the declaration will be rejected by \" +\n \"Postgres. Change it deliberately, when you can watch it:\\n\" +\n ` ALTER TABLE \"${schema}\".\"${table}\" ALTER COLUMN \"${drift.column}\" ` +\n `TYPE ${drift.declared} USING \"${drift.column}\"::${drift.declared};`\n );\n}\n","/**\n * The additive statements that bring a database up to a {@link SchemaPlan}.\n *\n * ## Why additive-only, forever\n *\n * This runs unattended, at boot, against a database with customers' data in it,\n * with no human reading a diff. So it may only ever do things that cannot lose\n * data: create a missing table, add a missing column, create a missing enum\n * type, add an enum value, create an index.\n *\n * It will **never** drop a table or a column, narrow a type, or alter a\n * constraint at boot. A removed field leaves its column behind; a renamed field\n * looks like an addition and the old column stays. That is the correct trade\n * for an automated path — the alternative is an unattended process that can\n * silently destroy a column. Destructive changes stay a deliberate,\n * human-reviewed migration. Because of that, this is safe to run on every boot,\n * and re-running it is a no-op.\n *\n * ## What changed when the planner arrived\n *\n * This used to read `Property` itself — its own reading of the type, of\n * `validation`, of relations, of enums — and disagreed with `db push` about all\n * of them. The audit found a required `author_id` that was NOT NULL after a\n * push and nullable after a boot, on the one path with no developer in the\n * loop. Now it reads the same {@link ColumnPlan} the DDL renderer does, so a\n * column's type, default, uniqueness and foreign key are decided once. What is\n * left here is the half that is genuinely this path's own: *what the database\n * already has*, and which of the planned constraints are safe to apply to it.\n */\nimport type { ColumnPlan, SchemaPlan, TablePlan } from \"./types\";\nimport { renderColumnDefinition, renderEnableRls, renderPgType, renderPrimaryKeyConstraint } from \"./render-ddl\";\nimport { quoteSqlLiteral } from \"./plan-schema\";\nimport { SET_UPDATED_AT_FN, triggerStatements } from \"./updated-at-trigger\";\nimport {\n SEARCH_STAMP_PREFIX,\n SEARCH_TEXT_FN,\n SEARCH_UNACCENT_FN,\n searchColumnStamps,\n searchIndexStatements\n} from \"../search-column\";\nimport { collectionIndexStatement, sortIndexSpecs } from \"../collection-index\";\nimport { vectorIndexStatement, type SkippedVectorIndex } from \"../vector-index\";\nimport { typesAgree, type ColumnTypeDrift } from \"../column-type-drift\";\n\n/** Postgres identifiers this module is willing to interpolate. */\nconst SAFE_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_$]*$/;\n\n/**\n * Validation used to run only on the names read back OUT of the catalogue,\n * which is the direction that cannot hurt anyone: those came from Postgres. The\n * names going IN — the schema and table a collection declares — were quoted and\n * concatenated on trust, and quoting is not escaping. A table named\n * `x\" (id int); DROP TABLE users; --` closes the quote and the statement, and\n * the whole thing runs on the owner connection, which is the one connection in\n * the system that is exempt from every RLS policy.\n *\n * That is only a config file on a self-hosted project, where the person writing\n * it could run the SQL directly anyway. It is not only a config file where a\n * collection can be defined over the wire — the live-schema and source editors\n * both do that — and there the caller is an admin on the app, not an operator\n * of the database.\n */\nexport function assertSafeIdentifier(value: string, what: string): string {\n if (!SAFE_IDENTIFIER.test(value)) {\n throw new Error(`Refusing to build SQL with an unsafe ${what}: ${JSON.stringify(value)}`);\n }\n return value;\n}\n\n/** What the database currently has, as the planner needs it. */\nexport interface ExistingSchema {\n /** `schema.table` → set of column names. */\n tables: Map<string, Set<string>>;\n /** `schema.typename` of every enum type that already exists. */\n enums: Set<string>;\n /**\n * `schema.table.constraint` of every constraint that already exists.\n *\n * Optional so a caller that only cares about tables can still build one by\n * hand; absent is read as \"none known\", which at worst re-attempts a\n * constraint that then fails harmlessly as a duplicate.\n */\n constraints?: Set<string>;\n /**\n * `schema.table.column` → that column's comment, for the columns that have\n * one. This is where a generated search column's fingerprint lives.\n */\n columnComments?: Map<string, string>;\n /** `schema.typename` → its labels, in `enumsortorder`. */\n enumValues?: Map<string, string[]>;\n /** `schema.table.column` of every NOT NULL column. */\n notNullColumns?: Set<string>;\n /** `schema.table.column` of every column that has a DEFAULT. */\n columnDefaults?: Set<string>;\n /** `schema.table` of every table that holds at least one row. */\n populatedTables?: Set<string>;\n /**\n * `schema.table.column` → the type Postgres reports, as `udt_name` (plus a\n * modifier where one distinguishes two columns of the same family, e.g.\n * `numeric(10,2)`).\n */\n columnTypes?: Map<string, string>;\n /** `schema.table.trigger` of every trigger that already exists. */\n triggers?: Set<string>;\n /**\n * `schema.name` of every index (any relation of kind `i`/`I`) that already\n * exists. Absent means unknown, and every index is planned as before —\n * `IF NOT EXISTS` makes that correct, just not free.\n */\n indexes?: Set<string>;\n /**\n * `schema.name` of every index whose `pg_index.indisvalid` is false — the\n * husk a `CREATE INDEX CONCURRENTLY` leaves behind when it is cancelled,\n * times out, or loses a race with a peer. Such an index is in\n * {@link indexes} too, maintained on every write and used by no query, and\n * `IF NOT EXISTS` skips it by name forever. Absent means unknown.\n */\n invalidIndexes?: Set<string>;\n /**\n * Names of the extensions installed in the database. `CREATE EXTENSION IF\n * NOT EXISTS` skips by name whatever schema the extension is in, so the name\n * is the whole test. Absent means unknown.\n */\n extensions?: Set<string>;\n /**\n * {@link HELPER_FUNCTIONS} as they are in the catalogue: `schema.name(args)`\n * → {@link describeHelperFunction} of what `CREATE OR REPLACE` would set.\n * Absent means unknown, and every helper is re-created as before.\n */\n functions?: Map<string, string>;\n}\n\n/**\n * The functions the plan creates with `CREATE OR REPLACE`, which the catalogue\n * reader looks up so a boot that finds them unchanged plans nothing.\n *\n * A helper missing from this list is not wrong, only re-created on every boot,\n * which is what all of them were before.\n */\nexport const HELPER_FUNCTIONS: readonly string[] = [SEARCH_TEXT_FN, SEARCH_UNACCENT_FN, SET_UPDATED_AT_FN];\n\n/**\n * Everything `CREATE OR REPLACE FUNCTION` sets on a function, as one string.\n *\n * Built from the catalogue by the reader and from the statement by\n * {@link parseHelperFunction}, so the two meet in one shape. It covers every\n * attribute a `CREATE OR REPLACE` resets — body, language, volatility,\n * strictness, parallel safety, security, leakproofness, cost and `SET` options —\n * so a function someone altered by hand (`ALTER FUNCTION … VOLATILE`) still\n * compares unequal and is re-created, exactly as the unconditional statement\n * used to re-create it.\n */\nexport interface HelperFunctionFacts {\n returns: string;\n language: string;\n /** `provolatile`: `i`mmutable, `s`table, `v`olatile. */\n volatility: string;\n strict: boolean;\n /** `proparallel`: `s`afe, `r`estricted, `u`nsafe. */\n parallel: string;\n securityDefiner: boolean;\n leakproof: boolean;\n cost: number;\n /** Whether the function carries any `SET` options (`proconfig`). */\n hasConfig: boolean;\n /** `prosrc`: the text between the dollar quotes, exactly. */\n body: string;\n}\n\nexport const helperFunctionKey = (schema: string, name: string, args: string): string =>\n `${schema}.${name}(${args.replace(/\\s+/g, \" \").trim().toLowerCase()})`;\n\nexport const describeHelperFunction = (facts: HelperFunctionFacts): string =>\n JSON.stringify([\n facts.returns.toLowerCase(), facts.language.toLowerCase(), facts.volatility, facts.strict, facts.parallel,\n facts.securityDefiner, facts.leakproof, Number(facts.cost), facts.hasConfig, facts.body\n ]);\n\nconst HELPER_FUNCTION_RE = new RegExp(\n \"^\\\\s*CREATE OR REPLACE FUNCTION\\\\s+([A-Za-z_]\\\\w*)\\\\.([A-Za-z_]\\\\w*)\\\\(([^)]*)\\\\)\" +\n \"\\\\s+RETURNS\\\\s+([\\\\w\\\\[\\\\]]+)\\\\s+LANGUAGE\\\\s+(\\\\w+)\" +\n \"((?:\\\\s+(?:IMMUTABLE|STABLE|VOLATILE|STRICT|PARALLEL\\\\s+(?:SAFE|RESTRICTED|UNSAFE)))*)\" +\n \"\\\\s+AS\\\\s+\\\\$\\\\$([\\\\s\\\\S]*)\\\\$\\\\$;?\\\\s*$\",\n \"i\"\n);\n\n/**\n * What a helper's `CREATE OR REPLACE FUNCTION` statement would leave in the\n * catalogue, or `undefined` for a statement this does not recognise — which is\n * then planned unconditionally, as every statement was before.\n *\n * Only ever given statements this package renders (`searchHelperFunctions`,\n * `setUpdatedAtFunction`), so a strict pattern is the right tool: anything it\n * does not match exactly falls back to the safe behaviour.\n */\nexport function parseHelperFunction(statement: string): { key: string; definition: string } | undefined {\n const match = HELPER_FUNCTION_RE.exec(statement);\n if (!match) return undefined;\n const [, schema, name, args, returns, language, attributes, body] = match;\n const words = attributes.toUpperCase();\n const parallel = /PARALLEL\\s+SAFE/.test(words) ? \"s\" : /PARALLEL\\s+RESTRICTED/.test(words) ? \"r\" : \"u\";\n const volatility = /\\bIMMUTABLE\\b/.test(words) ? \"i\" : /\\bSTABLE\\b/.test(words) ? \"s\" : \"v\";\n return {\n key: helperFunctionKey(schema, name, args),\n definition: describeHelperFunction({\n returns,\n language,\n volatility,\n strict: /\\bSTRICT\\b/.test(words),\n parallel,\n securityDefiner: false,\n leakproof: false,\n // Postgres's default for every language but C and internal.\n cost: 100,\n hasConfig: false,\n body\n })\n };\n}\n\n/** The name a rendered `CREATE [UNIQUE] INDEX [CONCURRENTLY] IF NOT EXISTS \"<name>\"` creates. */\nconst indexNameOf = (statement: string): string | undefined =>\n /^\\s*CREATE\\s+(?:UNIQUE\\s+)?INDEX\\s+(?:CONCURRENTLY\\s+)?IF\\s+NOT\\s+EXISTS\\s+\"([^\"]+)\"/i.exec(statement)?.[1];\n\n/** The extension a rendered `CREATE EXTENSION IF NOT EXISTS <name> …` installs. */\nconst extensionNameOf = (statement: string): string | undefined =>\n /^\\s*CREATE\\s+EXTENSION\\s+IF\\s+NOT\\s+EXISTS\\s+\"?([\\w-]+)\"?/i.exec(statement)?.[1];\n\n/**\n * How far the planner may go in making the database's constraints match the\n * configuration.\n *\n * - `additive` — the boot default. Columns, tables, indexes and enum values are\n * created; no existing column's constraints are touched. Unattended boots run\n * against customer data with nobody reading a diff, and a database adopted by\n * introspection legitimately carries NOT NULL on columns the generated\n * collection leaves optional (`introspect-db-logic` withholds `required` from\n * a column with a default or a trigger behind it). Converging there would\n * strip real constraints on first boot.\n * - `converge` — the live schema editor. Every statement is planned, shown to\n * the person making the change, and applied only once they confirm it.\n */\nexport type ConstraintPolicy = \"additive\" | \"converge\";\n\nexport interface DiffOptions {\n /** Defaults to `additive`. See {@link ConstraintPolicy}. */\n constraints?: ConstraintPolicy;\n /**\n * Plan every index a table that already exists needs — declared, search\n * and vector, missing or left INVALID under its name — as a\n * {@link DeferredIndexBuild} rather than an action, as the derived\n * foreign-key indexes always are. `ensureCollectionTables` sets it: it\n * hands them to `buildDeferredIndexes`, which builds them on a connection\n * of its own with no `statement_timeout`, and drops and rebuilds a husk.\n *\n * Absent — the live schema editor, which applies `statements` as text —\n * they stay `CREATE INDEX CONCURRENTLY IF NOT EXISTS` actions, as before.\n */\n deferLiveIndexes?: boolean;\n}\n\nexport interface EnsureAction {\n kind: \"create-enum\" | \"create-table\" | \"enable-rls\" | \"add-column\" | \"add-constraint\" | \"rename-column\"\n | \"create-extension\" | \"create-function\" | \"create-index\" | \"comment-column\"\n | \"add-enum-value\" | \"set-not-null\" | \"drop-not-null\" | \"set-default\" | \"create-trigger\";\n /** Qualified target, for logging: `public.posts` or `public.posts.title`. */\n target: string;\n sql: string;\n}\n\n/** A NOT NULL column with no default that no property declares. */\nexport interface OrphanedRequiredColumn {\n /** `schema.table`. */\n table: string;\n /** The column left behind. */\n column: string;\n /** The collection that no longer declares it. */\n slug: string;\n}\n\n/** A constraint the configuration asks for that the planner is not applying. */\nexport interface WithheldConstraint {\n /** `schema.table.column`. */\n target: string;\n kind: \"not-null\" | \"unique\";\n /**\n * Why, in a sentence that names the obstacle rather than the rule. The\n * reader is looking at a column that is nullable when they asked for\n * required, or accepts duplicates when they asked for unique, and needs to\n * know what to do about it.\n */\n reason: string;\n /** What would make it applicable. */\n remedy: string;\n}\n\n/**\n * A generated search column built from a `search` block that has since changed.\n *\n * Reported instead of applied because the two ways to apply it are both worse\n * than stopping. `ALTER COLUMN … SET EXPRESSION` exists only on PG17+ and\n * rewrites the table either way; `DROP COLUMN` + `ADD COLUMN` rewrites it under\n * an ACCESS EXCLUSIVE lock and rebuilds the GIN index. This module runs\n * unattended against live customer data with nobody reading a diff, so a\n * multi-minute outage is not a decision it may take on its own.\n *\n * Not applying it silently is not an option either: a collection that added a\n * field, flipped `unaccent` or raised a weight kept indexing the *old* set\n * forever, and the only symptom was searches returning nothing for content\n * plainly in the row.\n */\nexport interface SearchColumnDrift {\n /** `schema.table`. */\n table: string;\n column: string;\n /** The fingerprint recorded on the column. */\n found: string;\n /** The fingerprint the current `search` block computes. */\n expected: string;\n /** The statements that would rebuild the column, for the operator to run. */\n rebuild: string[];\n}\n\n/** A relation column whose old and new spellings both plausibly apply. */\nexport interface LegacyForeignKey {\n /** `schema.table`. */\n table: string;\n /** The name the current rule derives, and what this plan would create. */\n expected: string;\n /** The name the old rule derived, which the table already has. */\n legacy: string;\n}\n\n/**\n * An index a table that already exists is missing, or holds INVALID — a\n * derived foreign-key index always, and under\n * {@link DiffOptions.deferLiveIndexes} a declared, search or vector one too.\n *\n * Not an {@link EnsureAction}, because it must not hold the boot: on a table\n * with millions of rows `CREATE INDEX CONCURRENTLY` takes minutes, and every\n * minute of it would be a minute the deployment is not serving. The caller\n * builds these after the fact — boot in the background once it serves, every\n * other caller inline after the actions — through `buildDeferredIndexes`. A\n * {@link unique} one is the exception: it is built before the ensure returns.\n *\n * Kept out of {@link EnsurePlan.statements} for the same reason: a caller that\n * applies statements as text (the live schema editor) may run them inside a\n * transaction, which `CONCURRENTLY` refuses.\n */\nexport interface DeferredIndexBuild {\n schema: string;\n /** Bare table name. */\n table: string;\n indexName: string;\n /** `CREATE [UNIQUE] INDEX CONCURRENTLY IF NOT EXISTS …;` */\n sql: string;\n /**\n * The catalogue already holds an INVALID index under this name, which has\n * to be dropped before the build — `IF NOT EXISTS` would skip it.\n */\n invalid: boolean;\n /**\n * A declared `unique: true` index. Not an optimization but the collection\n * promising two rows cannot share a value, so `ensureCollectionTables`\n * builds it before it returns — never after the server is serving — and\n * fails when it cannot, as it did when this was an action. Absent is false.\n */\n unique?: boolean;\n}\n\nexport interface EnsurePlan {\n actions: EnsureAction[];\n /** Every statement, in dependency order. Empty when the schema is current. */\n statements: string[];\n /**\n * Relation columns this plan is about to create where the table already\n * carries the same column under its pre-singularization name.\n *\n * Reported rather than silently handled: the ensure is additive, so it\n * would add `category_id` beside a populated `categorie_id` and the\n * relation would then read the new, empty one. No statement fails, no table\n * is missing, and the only symptom is relations resolving to nothing.\n */\n legacyForeignKeys: LegacyForeignKey[];\n /** @see SearchColumnDrift */\n searchDrift: SearchColumnDrift[];\n /**\n * Generated search columns that exist but carry no fingerprint — created\n * before this check existed, or by `search.sql` on an older CLI. The plan\n * stamps them so the *next* change is detectable.\n */\n searchAdopted: { table: string; column: string }[];\n /**\n * Vector columns this plan is deliberately leaving unindexed, because\n * pgvector cannot build an ANN index that wide. Reported rather than\n * thrown: the column is valid, storable and searchable.\n */\n vectorIndexSkipped: SkippedVectorIndex[];\n /**\n * Constraints the configuration asks for that this plan is not applying,\n * and why. Every one of these was previously withheld in silence.\n */\n withheldConstraints: WithheldConstraint[];\n /**\n * Columns whose type in the database is in a different family from the one\n * the collection declares. Reported and never acted on — this is the one\n * divergence that presents as a *working* deploy.\n */\n columnTypeDrift: ColumnTypeDrift[];\n /**\n * Columns that are NOT NULL, have no default, and that no property declares\n * any more — so every insert through the API is rejected for a field the\n * author cannot name, because they already deleted it. This is what a\n * **rename** looks like to an additive provisioner. Reported, never acted\n * on: dropping the column is the right fix roughly always and destructive\n * exactly once, which is not a decision to take unattended.\n */\n orphanedRequiredColumns: OrphanedRequiredColumn[];\n /** @see DeferredIndexBuild */\n deferredIndexes: DeferredIndexBuild[];\n}\n\n/**\n * Whether a column is part of the table's primary key.\n *\n * Those are created with the table, in step 2, and never added afterwards: a\n * key column missing from a table that already exists cannot be made part of\n * its key by an additive change, so there is nothing safe to plan for one.\n *\n * A relation's entry for a column a declared property owns is not the column —\n * it carries only the constraint — so it is not a key column either, or the\n * key would be created twice.\n */\nconst isKeyColumn = (table: TablePlan, column: ColumnPlan): boolean =>\n !column.columnOwnedByProperty && table.primaryKey.includes(column.column);\n\n/**\n * A declared key whose default is the built-in uuid generator: the default\n * `isId: \"uuid\"` plans. It is the one declared-key default boot sets on an\n * existing table, because it is the one that cannot fail there.\n */\nconst isUuidKeyDefault = (column: ColumnPlan): boolean =>\n column.source.kind === \"property\" &&\n column.default?.kind === \"sql\" &&\n column.default.expression === \"gen_random_uuid()\";\n\n/** Which pass a column belongs to. The order below is the order they run in. */\nconst isScalarProperty = (table: TablePlan, column: ColumnPlan): boolean =>\n (column.source.kind === \"property\" || column.source.kind === \"implicit-id\") && !isKeyColumn(table, column);\n\n/**\n * `ENABLE ROW LEVEL SECURITY` on a table this plan has just created — the\n * statement immediately after its `CREATE TABLE`, never later.\n *\n * The user role every authenticated request runs as holds DML on a new table\n * the moment it exists: the schema's default privileges grant it, and boot's\n * `ensureAppRole` grants it on every table before the policies run. The\n * policies — which also switch RLS on — come after the driver and auth have\n * initialised, so any failure in between left a table with full DML and no row\n * filtering, served by every process in the deployment that does not\n * provision. Born locked, a table denies until its policies arrive, which is\n * the state a failure is allowed to leave behind. Idempotent, so a table a\n * concurrent boot created gets the same statement and nothing changes.\n */\nconst lockAction = (table: TablePlan): EnsureAction => ({\n kind: \"enable-rls\",\n target: table.qualified,\n sql: renderEnableRls(table)\n});\n\nexport function diffPlanAgainstCatalogue(\n plan: SchemaPlan,\n existing: ExistingSchema,\n options: DiffOptions = {}\n): EnsurePlan {\n const constraintPolicy: ConstraintPolicy = options.constraints ?? \"additive\";\n const actions: EnsureAction[] = [];\n const withheldConstraints: WithheldConstraint[] = [];\n const columnTypeDrift: ColumnTypeDrift[] = [];\n const legacyForeignKeys: LegacyForeignKey[] = [];\n const searchDrift: SearchColumnDrift[] = [];\n const searchAdopted: { table: string; column: string }[] = [];\n\n const collectionTables = plan.tables.filter(t => t.kind === \"collection\");\n const junctionTables = plan.tables.filter(t => t.kind === \"junction\");\n\n // 1. Enum types. `CREATE TYPE` has no IF NOT EXISTS, so an existing type is\n // skipped by name rather than guarded in SQL.\n for (const enumPlan of plan.enums) {\n assertSafeIdentifier(enumPlan.schema, \"schema name\");\n if (existing.enums.has(enumPlan.qualified)) {\n // The type is there, but that says nothing about its *values*.\n // Skipping the whole type by name is what made an added enum value\n // vanish: nothing was planned, the boot reported success, and the\n // first row using the value was rejected by a type that had never\n // heard of it. `ADD VALUE` is the one alteration Postgres offers\n // here, it is purely additive, and it is idempotent.\n //\n // `enumValues` absent means the caller built the schema by hand and\n // does not know the values; skip by name as before.\n const current = existing.enumValues?.get(enumPlan.qualified);\n if (!current) continue;\n for (const value of enumPlan.labels) {\n if (current.includes(value)) continue;\n actions.push({\n kind: \"add-enum-value\",\n target: `${enumPlan.qualified}.${value}`,\n // Not inside a transaction with any use of the value:\n // Postgres refuses to read a value added by the transaction\n // still adding it. The applier runs these one statement at a\n // time, which is what makes it legal.\n sql: `ALTER TYPE \"${enumPlan.schema}\".\"${enumPlan.name}\" ADD VALUE IF NOT EXISTS ${quoteSqlLiteral(value)};`\n });\n }\n continue;\n }\n actions.push({\n kind: \"create-enum\",\n target: enumPlan.qualified,\n sql: `CREATE TYPE \"${enumPlan.schema}\".\"${enumPlan.name}\" AS ENUM (${enumPlan.labels.map(quoteSqlLiteral).join(\", \")});`\n });\n }\n\n // 1b. Extensions and helper functions, before the tables and columns that\n // reference them: a generated column's expression is resolved when the\n // column is created, so a table whose search column calls\n // `rebase_search_text` cannot be added before that function exists.\n // Both forms are idempotent, and a boot against a database that already\n // has them plans nothing: each is looked up in the catalogue first. An\n // unconditional `CREATE OR REPLACE` rewrote the function's `pg_proc` row\n // and invalidated every cached plan that calls it, on every replica, on\n // every boot — and was reported as a change applied.\n for (const statement of plan.extensions) {\n const extension = extensionNameOf(statement);\n if (extension && existing.extensions?.has(extension)) continue;\n actions.push({\n kind: \"create-extension\",\n target: statement.replace(/^CREATE EXTENSION IF NOT EXISTS |;$/g, \"\"),\n sql: statement\n });\n }\n for (const statement of plan.functions) {\n const helper = parseHelperFunction(statement);\n if (helper && existing.functions?.get(helper.key) === helper.definition) continue;\n actions.push({ kind: \"create-function\", target: functionTarget(statement), sql: statement });\n }\n\n // 2. Missing tables. Only the key columns are created here; every other\n // column is added by step 3, so a new table and an existing table that\n // gained a field travel the exact same code path. One way to build a\n // column means one way for it to be wrong.\n //\n // A composite key's columns all arrive here, with the one `PRIMARY KEY`\n // constraint naming them: a primary key can only be declared when the\n // table is created, and a table created with the first key column alone\n // would never gain the rest as part of its key.\n const created = new Set<string>();\n for (const table of collectionTables) {\n if (existing.tables.has(table.qualified) || created.has(table.qualified)) continue;\n created.add(table.qualified);\n const schema = assertSafeIdentifier(table.schema, \"schema name\");\n const name = assertSafeIdentifier(table.table, \"table name\");\n const keyColumns = table.columns.filter(c => isKeyColumn(table, c));\n const definitions = keyColumns.length > 0\n ? keyColumns.map(c => `\"${assertSafeIdentifier(c.column, \"column name\")}\" ${renderColumnDefinition(c)}`)\n : ['\"id\" TEXT PRIMARY KEY'];\n if (keyColumns.length > 1) definitions.push(renderPrimaryKeyConstraint(table));\n actions.push({\n kind: \"create-table\",\n target: table.qualified,\n sql: `CREATE TABLE IF NOT EXISTS \"${schema}\".\"${name}\" (${definitions.join(\", \")});`\n });\n actions.push(lockAction(table));\n }\n\n // 2b. Junction tables behind many-to-many relations. No collection declares\n // them, so until they existed an m2m write had nowhere to land and the\n // junction's derived RLS had nothing to attach to. Both columns are\n // created here — unlike a collection table, whose CREATE declares only\n // the identity column — because there is nothing else to them.\n for (const table of junctionTables) {\n if (existing.tables.has(table.qualified) || created.has(table.qualified)) continue;\n created.add(table.qualified);\n const columns = table.columns\n .map(c => `\"${c.column}\" ${renderColumnDefinition(c)}`)\n .join(\", \");\n actions.push({\n kind: \"create-table\",\n target: table.qualified,\n sql: `CREATE TABLE IF NOT EXISTS \"${table.schema}\".\"${table.table}\" (${columns}` +\n `, ${renderPrimaryKeyConstraint(table)});`\n });\n actions.push(lockAction(table));\n }\n\n const addColumn = (table: TablePlan, column: string, definition: string): void => {\n if (existing.tables.get(table.qualified)?.has(column)) return;\n // Same reason as `assertSafeIdentifier` above: a column name reaching\n // here came from a property declaration, which on the editor paths came\n // over the wire. `definition` is built by the renderer from a closed set\n // of type mappings and is not caller text.\n assertSafeIdentifier(column, \"column name\");\n actions.push({\n kind: \"add-column\",\n target: `${table.qualified}.${column}`,\n sql: `ALTER TABLE \"${table.schema}\".\"${table.table}\" ADD COLUMN IF NOT EXISTS \"${column}\" ${definition};`\n });\n };\n\n /**\n * Move a column that is only missing because it was renamed.\n *\n * Returns true when it handled the column, so the caller skips the ordinary\n * ADD. Adding would be the wrong move and a quiet one: the data is in the\n * old column, `ADD COLUMN` creates the new one empty beside it, every\n * statement succeeds, and the relation reads the empty one. A rename is\n * metadata-only in Postgres, keeps the values, and carries the column's\n * indexes and constraints with it.\n */\n const renameLegacyColumn = (table: TablePlan, column: ColumnPlan): boolean => {\n const present = existing.tables.get(table.qualified);\n if (!column.legacyColumn || !present) return false;\n if (present.has(column.column) || !present.has(column.legacyColumn)) return false;\n legacyForeignKeys.push({ table: table.qualified, expected: column.column, legacy: column.legacyColumn });\n actions.push({\n kind: \"rename-column\",\n target: `${table.qualified}.${column.column}`,\n sql: `ALTER TABLE \"${table.schema}\".\"${table.table}\" RENAME COLUMN \"${column.legacyColumn}\" TO \"${column.column}\";`\n });\n return true;\n };\n\n /**\n * A `NOT NULL` is safe exactly when it cannot fail against rows that are\n * already there, and there are three ways to know that:\n *\n * - the table is being created by this plan (no rows yet);\n * - the table exists and is empty;\n * - the column arrives with a DEFAULT, which Postgres backfills into every\n * existing row as part of ADD COLUMN.\n *\n * `populatedTables` absent means the caller does not know, and not knowing\n * has to read as \"assume rows\" — the other direction emits a NOT NULL that\n * is checked against live data and aborts the boot. Written as an explicit\n * `!== undefined` because the optional-chain form quietly says *empty* when\n * the fact is missing, which is the wrong way to be wrong.\n */\n const tableIsEmpty = (table: TablePlan): boolean =>\n existing.populatedTables !== undefined\n && existing.tables.has(table.qualified)\n && !existing.populatedTables.has(table.qualified);\n\n /**\n * The column is there and holds a different kind of value than the\n * collection says it does. Reported, never altered: an unattended `ALTER\n * COLUMN … TYPE` over customer data is not a thing to do quietly, and the\n * *quiet* is what this fixes. A deploy that changed a `columnType` used to\n * report success while the column stayed as it was, and the divergence\n * surfaced later as every write failing. A column whose definition is owned\n * elsewhere (auth, or a generated search column) is not compared: the\n * declaration there is a rendered string rather than a type, and auth's own\n * reconcile owns it.\n */\n const reportTypeDrift = (table: TablePlan, column: ColumnPlan): void => {\n const key = `${table.qualified}.${column.column}`;\n if (existing.tables.get(table.qualified)?.has(column.column) !== true || column.sqlDefinition) return;\n const declaredType = renderPgType(column.type);\n const actualType = existing.columnTypes?.get(key);\n if (actualType && !typesAgree(declaredType, actualType)) {\n columnTypeDrift.push({ table: table.qualified, column: column.column, declared: declaredType, actual: actualType });\n }\n };\n\n /**\n * `SET DEFAULT` on a column that is already there and has none.\n *\n * A DEFAULT binds future writes only, so `SET DEFAULT` cannot fail on data\n * and cannot touch a row that already exists. That makes it the one\n * convergence this path may do unattended — and it has to, because\n * `ADD COLUMN IF NOT EXISTS` is a no-op against an existing column, so a\n * `defaultValue` added to a live collection would otherwise never reach the\n * database at all.\n */\n const planMissingDefault = (table: TablePlan, column: ColumnPlan): void => {\n if (!column.default || column.default.kind === \"identity\" || column.generated) return;\n const key = `${table.qualified}.${column.column}`;\n if (existing.columnDefaults?.has(key)) return;\n const expression = column.default.kind === \"sql\" ? column.default.expression : column.default.sql;\n actions.push({\n kind: \"set-default\",\n target: key,\n sql: `ALTER TABLE \"${table.schema}\".\"${table.table}\" ALTER COLUMN \"${column.column}\" SET DEFAULT ${expression};`\n });\n };\n\n /**\n * Plan one column, with the constraints this database can safely take.\n *\n * The definition comes from the same renderer `schema.sql` uses, minus the\n * constraints that are checked against live rows. That subtraction is the\n * whole of what this path is allowed to decide for itself.\n */\n const planColumn = (table: TablePlan, column: ColumnPlan): void => {\n const key = `${table.qualified}.${column.column}`;\n const columnExists = existing.tables.get(table.qualified)?.has(column.column) === true;\n const fresh = created.has(table.qualified);\n const empty = tableIsEmpty(table);\n const required = !column.nullable;\n const hasDefault = column.default !== undefined;\n\n reportTypeDrift(table, column);\n\n // A table this run is creating has no rows yet, so the constraints\n // `db push` writes are free to apply. On a table that already exists\n // they are checked against live rows, so each is applied only where it\n // cannot fail.\n //\n // NOT NULL needs every existing row to get a value: an empty table, or\n // a default that backfills them.\n //\n // UNIQUE needs every existing row to get a *different* value, and a\n // column being added gets either its default in every row — the same\n // value, so a second row fails — or NULL in every row, and NULLs are\n // distinct. So it holds on an empty table, and on any table when the\n // column arrives with no default. A column that is already there is\n // not touched: whether it carries a unique constraint or index under\n // some other name is not something the catalogue read here says.\n const notNullIsSafe = fresh || empty || hasDefault;\n const uniqueIsSafe = fresh || empty || !hasDefault;\n const applicable: ColumnPlan = {\n ...column,\n unique: column.unique && !columnExists && uniqueIsSafe,\n nullable: column.nullable || !(notNullIsSafe || columnExists)\n };\n if (column.unique && !columnExists && !uniqueIsSafe) {\n withheldConstraints.push({\n target: key,\n kind: \"unique\",\n reason:\n `\"${column.column}\" is unique, but \"${table.qualified}\" ` +\n (existing.populatedTables === undefined ? \"may already hold rows\" : \"already holds rows\") +\n \" and the column's default would give every one of them the same value, so UNIQUE \" +\n \"would fail as it was added.\",\n remedy:\n \"Give each existing row its own value, then add the constraint — \" +\n `\\`ALTER TABLE \"${table.schema}\".\"${table.table}\" ADD UNIQUE (\"${column.column}\")\\` — ` +\n \"or drop the property's default.\"\n });\n }\n if (required && !columnExists && !notNullIsSafe) {\n withheldConstraints.push({\n target: key,\n kind: \"not-null\",\n reason: column.source.kind === \"relation\" || column.source.kind === \"reference\"\n ? `\"${column.column}\" is a required link, but \"${table.qualified}\" already holds rows and ` +\n \"the column has no default to backfill them with, so NOT NULL would be checked \" +\n \"against data that does not have a value yet.\"\n : `\"${column.column}\" is required, but \"${table.qualified}\" already holds rows and the column ` +\n \"has no default to backfill them with, so NOT NULL would be checked \" +\n \"against data that does not have a value yet.\",\n remedy: column.source.kind === \"relation\" || column.source.kind === \"reference\"\n ? \"Backfill the column, then add the constraint — or make the relation optional.\"\n : \"Backfill the column, then add the constraint — or give the property a \" +\n \"default so every existing row gets one.\"\n });\n }\n addColumn(table, column.column, renderColumnDefinition(applicable));\n\n if (!columnExists) return;\n\n planMissingDefault(table, column);\n\n // Two directions on NOT NULL, and they are not equally safe — see\n // `ConstraintPolicy` for why neither runs at an unattended boot.\n if (constraintPolicy !== \"converge\") return;\n const isNotNull = existing.notNullColumns?.has(key) === true;\n if (required && !isNotNull) {\n if (empty) {\n actions.push({\n kind: \"set-not-null\",\n target: key,\n sql: `ALTER TABLE \"${table.schema}\".\"${table.table}\" ALTER COLUMN \"${column.column}\" SET NOT NULL;`\n });\n } else {\n withheldConstraints.push({\n target: key,\n kind: \"not-null\",\n reason:\n `\"${column.column}\" became required, but \"${table.qualified}\" holds rows and any of them ` +\n \"with no value would make SET NOT NULL fail.\",\n remedy:\n \"Backfill the column first — `UPDATE … SET \\\"\" + column.column +\n \"\\\" = … WHERE \\\"\" + column.column + \"\\\" IS NULL` — then apply this again.\"\n });\n }\n }\n if (!required && isNotNull) {\n // Loosening never fails and never loses data. It is here rather\n // than at boot because a database adopted by introspection carries\n // NOT NULL on columns the generated collection deliberately leaves\n // optional, and converging those unasked would drop constraints\n // nobody edited.\n actions.push({\n kind: \"drop-not-null\",\n target: key,\n sql: `ALTER TABLE \"${table.schema}\".\"${table.table}\" ALTER COLUMN \"${column.column}\" DROP NOT NULL;`\n });\n }\n };\n\n // 3. Scalar columns, then the auth columns the collection never mentions.\n // The scaffold's users collection describes 12 of the 14 columns auth\n // reads and writes, so planning only from properties left `is_anonymous`\n // and `tokens_valid_after` to `ensureAuthTablesExist` — which does create\n // them, but only because that function happens to run later in the same\n // boot. Planning them here makes this path self-contained.\n for (const table of collectionTables) {\n assertSafeIdentifier(table.schema, \"schema name\");\n assertSafeIdentifier(table.table, \"table name\");\n for (const column of table.columns) {\n if (!isScalarProperty(table, column)) continue;\n planColumn(table, column);\n }\n // The implicit key of a collection that declares none. It is created\n // with the table, so the only thing to plan is the default a table\n // made before the key had one is missing — without it, nothing can\n // insert a row there: no door sends a key that is not a property.\n //\n // A declared uuid key (`isId: \"uuid\"`) is the same case: no door sends\n // it, and a table adopted by introspection or made by hand may lack\n // its default. Only the built-in generator is converged. A SQL\n // strategy's function may not exist yet, and setting it here would\n // turn an insert-time failure into a boot failure.\n for (const column of table.columns) {\n if (!column.primaryKey) continue;\n if (column.source.kind !== \"implicit-id\" && !isUuidKeyDefault(column)) continue;\n if (existing.tables.get(table.qualified)?.has(column.column) !== true) continue;\n planMissingDefault(table, column);\n }\n for (const column of table.columns) {\n if (column.source.kind !== \"auth\") continue;\n addColumn(table, column.column, renderColumnDefinition(column));\n }\n }\n\n // 3a. The generated search columns.\n //\n // Adding a STORED generated column rewrites the table, which on a large\n // one is not free — but it is the same additive shape as every other\n // column here, and the alternative (leaving it out until someone runs a\n // migration) is a declared `search` block that silently does nothing.\n //\n // Changing one is not additive, and `ADD COLUMN IF NOT EXISTS` is a\n // no-op against a column that is already there — which is why a `search`\n // block that gained a field, flipped `unaccent` or moved a weight used\n // to be inert forever, on every path, with nothing logged. Each column\n // carries a fingerprint of the expression it was built from, and a\n // mismatch is reported rather than applied.\n for (const table of collectionTables) {\n if (!table.search) continue;\n const definitions = new Map(\n table.columns\n .filter(c => c.source.kind === \"search\")\n .map(c => [c.column, renderColumnDefinition(c)] as const)\n );\n for (const stamp of searchColumnStamps(table.search)) {\n const definition = definitions.get(stamp.column)!;\n const exists = existing.tables.get(table.qualified)?.has(stamp.column) === true;\n const recorded = existing.columnComments?.get(`${table.qualified}.${stamp.column}`);\n\n if (exists && recorded?.startsWith(SEARCH_STAMP_PREFIX) && recorded !== stamp.fingerprint) {\n searchDrift.push({\n table: table.qualified,\n column: stamp.column,\n found: recorded,\n expected: stamp.fingerprint,\n rebuild: [\n `ALTER TABLE \"${table.schema}\".\"${table.table}\" DROP COLUMN \"${stamp.column}\";`,\n `ALTER TABLE \"${table.schema}\".\"${table.table}\" ADD COLUMN \"${stamp.column}\" ${definition};`,\n stamp.sql\n ]\n });\n // The old stamp is the only evidence of what the column holds;\n // overwriting it here would erase the drift instead of fixing it.\n continue;\n }\n\n addColumn(table, stamp.column, definition);\n if (exists && recorded === undefined) {\n searchAdopted.push({ table: table.qualified, column: stamp.column });\n }\n if (recorded !== stamp.fingerprint) {\n actions.push({ kind: \"comment-column\", target: `${table.qualified}.${stamp.column}`, sql: stamp.sql });\n }\n }\n }\n\n // 3b. A junction that already existed, but is short a column. One created\n // above already carries both, so re-listing them would log two no-op\n // statements and inflate the count of changes applied.\n for (const table of junctionTables) {\n if (created.has(table.qualified)) continue;\n for (const column of table.columns) {\n if (renameLegacyColumn(table, column)) continue;\n // A payload column (`through.properties`) goes through `planColumn`,\n // the same pass a collection's scalar columns take. The bare\n // `ADD COLUMN <type>` below is right for the two key columns — they\n // are NOT NULL by the composite primary key and have no default —\n // and wrong for everything else: it would drop a `defaultValue`, and\n // it would either omit a NOT NULL the collection declared or apply\n // one to a junction that already holds rows without a value to\n // backfill them. `planColumn` is the one place that decides which of\n // those is safe, and it reports the constraint it withheld.\n if (column.source.kind === \"property\") {\n planColumn(table, column);\n continue;\n }\n // A key column whose type is not its endpoint's key type is a\n // foreign key that can never be added (42804) — said, like any\n // other column's drift, rather than failed on every boot.\n reportTypeDrift(table, column);\n addColumn(table, column.column, renderPgType(column.type));\n }\n }\n\n // 3c. The columns relation and reference properties own.\n for (const table of collectionTables) {\n for (const column of table.columns) {\n if (column.source.kind !== \"relation\" && column.source.kind !== \"reference\") continue;\n // A declared property already emits this column (an explicit\n // `postId` beside the `belongsTo` that uses it); the relation\n // contributes only the constraint, below.\n if (column.columnOwnedByProperty) continue;\n // A reference that is part of a composite key was created with\n // the table, in step 2.\n if (isKeyColumn(table, column)) continue;\n if (renameLegacyColumn(table, column)) continue;\n planColumn(table, column);\n }\n }\n\n // 4. Foreign keys, last: the tables and columns on both ends have to exist\n // first, and a constraint is the one thing here that can fail on data\n // rather than on schema, so nothing else depends on it.\n const knownConstraints = existing.constraints ?? new Set<string>();\n const plannedConstraints = new Set<string>();\n for (const table of [...collectionTables, ...junctionTables]) {\n for (const column of table.columns) {\n const fk = column.foreignKey;\n if (!fk) continue;\n const name = `${fk.schema}.${fk.table}.${fk.constraintName}`;\n if (knownConstraints.has(name) || plannedConstraints.has(name)) continue;\n plannedConstraints.add(name);\n actions.push({ kind: \"add-constraint\", target: name, sql: `${fk.sql};` });\n }\n }\n\n // 5. Indexes, after everything — the column has to exist, and this is the\n // one step that runs against a populated table for real work.\n //\n // CONCURRENTLY: a plain CREATE INDEX takes a lock that blocks writes for\n // the duration of the build, which on a live table is an outage. Each\n // statement here is issued on its own, outside any transaction, which is\n // the condition CONCURRENTLY requires.\n //\n // ...and only on a live table. It is withheld for a table this same plan\n // is creating: there are no rows to build from and no writer to block, so\n // the lock CONCURRENTLY avoids is a lock nobody would have taken. Worth\n // withholding rather than merely harmless, because CONCURRENTLY is the\n // one index form Postgres refuses inside a transaction block.\n const concurrent = (schema: string, table: string): boolean => !created.has(`${schema}.${table}`);\n // An index already in the catalogue under its name is not planned:\n // `IF NOT EXISTS` would skip it by that same name, so the outcome is the\n // same — minus a lock on the table to find that out, and minus a change\n // reported on a database that has none.\n const indexExists = (schema: string, name: string | undefined): boolean =>\n name !== undefined && existing.indexes?.has(`${schema}.${name}`) === true;\n // ...except on a table that already exists, where an index goes to the\n // builder instead (`deferredIndexes`, see `DeferredIndexBuild`): a\n // derived foreign-key index always, and under `deferLiveIndexes` every\n // other kind too. The builder also repairs the one case `IF NOT EXISTS`\n // cannot see — an index that is there by name and INVALID, because a\n // concurrent build of it was cancelled — so it is planned again.\n const deferredIndexes: DeferredIndexBuild[] = [];\n const toBuilder = (schema: string, table: string, derived: boolean): boolean =>\n concurrent(schema, table) && (derived || options.deferLiveIndexes === true);\n const deferIndex = (build: Omit<DeferredIndexBuild, \"invalid\">): void => {\n const invalid = existing.invalidIndexes?.has(`${build.schema}.${build.indexName}`) === true;\n if (indexExists(build.schema, build.indexName) && !invalid) return;\n deferredIndexes.push({ ...build, invalid });\n };\n /** A search or vector statement in its concurrent form, or undefined if it has no plain `CREATE INDEX IF NOT EXISTS`. */\n const concurrentForm = (statement: string): string | undefined => {\n const rewritten = statement.replace(\"CREATE INDEX IF NOT EXISTS\", \"CREATE INDEX CONCURRENTLY IF NOT EXISTS\");\n return rewritten === statement ? undefined : rewritten;\n };\n for (const table of collectionTables) {\n if (!table.search) continue;\n const { schema, table: tableName } = table.search;\n for (const statement of searchIndexStatements(table.search)) {\n const name = indexNameOf(statement);\n const sql = concurrentForm(statement);\n if (name !== undefined && sql !== undefined && toBuilder(schema, tableName, false)) {\n deferIndex({ schema, table: tableName, indexName: name, sql });\n continue;\n }\n if (indexExists(table.schema, name)) continue;\n actions.push({\n kind: \"create-index\",\n target: table.qualified,\n sql: concurrent(table.schema, table.table)\n ? statement.replace(\"CREATE INDEX IF NOT EXISTS\", \"CREATE INDEX CONCURRENTLY IF NOT EXISTS\")\n : statement\n });\n }\n }\n\n // ANN indexes for vector columns, on the same terms. A column too wide\n // for pgvector to index is reported, not planned — silence there would\n // read as \"indexed\" to anyone watching the boot.\n const vectorIndexSkipped: SkippedVectorIndex[] = [];\n for (const table of collectionTables) {\n if (!table.vector) continue;\n for (const spec of table.vector.specs) {\n const sql = concurrentForm(vectorIndexStatement(spec));\n if (sql !== undefined && toBuilder(spec.schema, spec.table, false)) {\n deferIndex({ schema: spec.schema, table: spec.table, indexName: spec.indexName, sql });\n continue;\n }\n if (indexExists(spec.schema, spec.indexName)) continue;\n actions.push({\n kind: \"create-index\",\n target: `${spec.schema}.${spec.table}`,\n sql: concurrent(spec.schema, spec.table)\n ? vectorIndexStatement(spec).replace(\"CREATE INDEX IF NOT EXISTS\", \"CREATE INDEX CONCURRENTLY IF NOT EXISTS\")\n : vectorIndexStatement(spec)\n });\n }\n vectorIndexSkipped.push(...table.vector.skipped);\n }\n\n // Declared indexes, on exactly the same terms. Boot has to emit these,\n // not just `db push`: the managed runtime provisions at boot and never\n // runs a push, so a push-only index would simply not exist there — and\n // nothing would say so. `contracts/derived-names.txt` states the rule\n // (\"Both, or it is a bug\") and the gate enforces it.\n //\n // `concurrently` is a parameter rather than a string replacement on the\n // rendered SQL: the `.replace(…)` above silently does nothing for a\n // UNIQUE index, whose text is `CREATE UNIQUE INDEX …`.\n //\n // The foreign-key indexes the plan derives go through here too, and on a\n // table that already exists they go to the builder whoever plans them\n // (see `toBuilder`). A junction table carries one — its target column —\n // which is why this walks both kinds of table.\n for (const spec of sortIndexSpecs([...collectionTables, ...junctionTables].flatMap(t => t.indexes))) {\n if (toBuilder(spec.schema, spec.table, spec.derived === \"foreign-key\")) {\n deferIndex({\n schema: spec.schema,\n table: spec.table,\n indexName: spec.indexName,\n sql: collectionIndexStatement(spec, { concurrently: true, ifNotExists: true }),\n ...(spec.unique ? { unique: true } : {})\n });\n continue;\n }\n if (indexExists(spec.schema, spec.indexName)) continue;\n actions.push({\n kind: \"create-index\",\n target: `${spec.schema}.${spec.table}`,\n sql: collectionIndexStatement(spec, {\n concurrently: concurrent(spec.schema, spec.table),\n ifNotExists: true\n })\n });\n }\n\n // 6. `autoValue: \"on_update\"` triggers. After the column, and idempotent by\n // DROP-then-CREATE rather than `CREATE OR REPLACE TRIGGER`, which exists\n // only on Postgres 14+.\n for (const table of collectionTables) {\n for (const trigger of table.triggers) {\n // Named after its table and column, so a trigger already in the\n // catalogue is this one — re-issuing it every boot would work and\n // would report a change on a database that has none.\n if (existing.triggers?.has(`${table.qualified}.${trigger.name}`)) continue;\n for (const statement of triggerStatements(trigger)) {\n actions.push({\n kind: \"create-trigger\",\n target: `${table.qualified}.${trigger.name}`,\n sql: statement\n });\n }\n }\n }\n\n // A column the database still requires that no property declares any more.\n //\n // Computed last, over the same live snapshot the rest of the plan read, and\n // only for tables this run did not create — a table created here has exactly\n // the columns the collection asked for, so there is nothing to orphan.\n //\n // Every element of the test matters. NOT NULL, because a nullable leftover\n // accepts a write and is merely untidy. No DEFAULT, because a leftover with\n // one is filled in for you. Not declared, because that is what makes it\n // unreachable — the author has no field to send.\n const orphanedRequiredColumns: OrphanedRequiredColumn[] = [];\n if (existing.notNullColumns && existing.columnDefaults) {\n for (const table of collectionTables) {\n const live = existing.tables.get(table.qualified);\n if (!live || created.has(table.qualified)) continue;\n const declared = new Set<string>();\n for (const column of table.columns) {\n declared.add(column.column);\n if (column.legacyColumn) declared.add(column.legacyColumn);\n }\n for (const column of live) {\n const key = `${table.qualified}.${column}`;\n if (declared.has(column)) continue;\n if (!existing.notNullColumns.has(key)) continue;\n if (existing.columnDefaults.has(key)) continue;\n orphanedRequiredColumns.push({ table: table.qualified, column, slug: table.slug! });\n }\n }\n }\n\n return {\n actions,\n statements: actions.map(a => a.sql),\n legacyForeignKeys,\n searchDrift,\n searchAdopted,\n vectorIndexSkipped,\n withheldConstraints,\n columnTypeDrift,\n orphanedRequiredColumns,\n deferredIndexes\n };\n}\n\n/** What a `CREATE FUNCTION` statement is called, for the action log. */\nconst functionTarget = (statement: string): string => {\n if (statement.includes(\"set_updated_at\")) return SET_UPDATED_AT_FN;\n return statement.includes(\"unaccent\") ? SEARCH_UNACCENT_FN : SEARCH_TEXT_FN;\n};\n","/**\n * Building the indexes a live table is missing — or holds INVALID — without\n * holding anything up: the derived foreign-key indexes, and the declared,\n * search and vector ones the boot-time ensure finds missing on a table that\n * already exists.\n *\n * ## Why not a boot action like an index on a new table\n *\n * A table that already exists may hold millions of rows, and an index over\n * them takes minutes to build. Boot-ensure's actions run before the server\n * listens, so an index planned there is minutes of a deployment not serving —\n * on the first boot after an upgrade that adds one to every `belongsTo` in the\n * project, that is every large table at once. So these are planned apart\n * (`DeferredIndexBuild`) and built here, after the boot, one at a time. A\n * UNIQUE one is the exception: `ensureCollectionTables` builds it here too, but\n * before it returns.\n *\n * ## The build\n *\n * `CREATE INDEX CONCURRENTLY`, outside any transaction (Postgres refuses it\n * inside one), on a connection of its own with `statement_timeout` lifted: the\n * pool's 30 s would cancel any build worth deferring. `CONCURRENTLY` takes\n * `SHARE UPDATE EXCLUSIVE`, which conflicts with no read and no write — only\n * with other DDL, `VACUUM` and `ANALYZE` — so live traffic never queues behind\n * it, however long it runs.\n *\n * The boot's lock budget still applies, to the one wait that matters: getting\n * the table at all. A table something else holds — another instance's build,\n * an `ALTER TABLE`, a `VACUUM FULL` — is probed for `BOOT_LOCK_TIMEOUT_MS` at a\n * time within the boot's retry budget, and left for the next boot when it\n * stays held. Once the build has the table, it waits for the transactions\n * already running against it with no deadline: a timeout there would only turn\n * a slow build into a failed one.\n *\n * ## The husk a failed build leaves\n *\n * A cancelled `CONCURRENTLY` — a timeout, a killed process, a deploy that\n * replaced the pod mid-build, two instances racing on one name — leaves the\n * index behind marked `indisvalid = false`: maintained on every write, used by\n * no query. And `IF NOT EXISTS` is a *name* check, so every later attempt\n * skips it and reports success. This reads `pg_index` instead: an invalid index\n * under our name is dropped (`DROP INDEX CONCURRENTLY`) and built again, once.\n * One that another backend is still building (`pg_stat_progress_create_index`)\n * is left to it.\n *\n * Nothing here is fatal. A failure is logged with the index named and the\n * reason, and the next boot plans the index again.\n */\nimport type { Pool, QueryConfig, QueryResultRow } from \"pg\";\nimport { logger } from \"@rebasepro/server\";\nimport { assertSafeIdentifier, type DeferredIndexBuild } from \"./plan/diff-plan\";\nimport { quoteSqlLiteral } from \"./plan/plan-schema\";\nimport { extractPgError } from \"../utils/pg-error-utils\";\nimport {\n atomicBatch,\n BOOT_LOCK_RETRY_BUDGET_MS,\n isLockTimeout,\n isMultiStatementRefusal,\n resolveBootLockOptions,\n sleep,\n type BootLockOptions\n} from \"./boot-ddl\";\n\n/**\n * A connection the builder may hold for as long as an index takes.\n *\n * `dedicated` says whether consecutive queries reach the same backend. Only\n * then can a session setting be relied on — and be reset afterwards: on a\n * pool, `SET statement_timeout = 0` would land on whichever connection served\n * it and stay there for the next request to inherit.\n */\nexport interface IndexBuildSession {\n query<T = unknown>(sql: string): Promise<{ rows: T[] }>;\n readonly dedicated: boolean;\n /** Hand the connection back. Called once, after the last index. */\n release(): void | Promise<void>;\n}\n\nexport type OpenIndexBuildSession = () => Promise<IndexBuildSession>;\n\n/** A client-side read timeout no index build reaches. A pool's is 60 s. */\nconst INDEX_BUILD_READ_TIMEOUT_MS = 24 * 60 * 60 * 1000;\n\n/** A `pg.Pool` — what `connect()` checks a client out of — and not a `pg.Client`, whose `connect()` opens a socket. */\nexport const isPgPool = (value: unknown): value is Pick<Pool, \"connect\" | \"totalCount\"> =>\n typeof value === \"object\" && value !== null && \"connect\" in value && typeof value.connect === \"function\"\n && \"totalCount\" in value;\n\n/**\n * Sessions checked out of a `pg.Pool`, one connection each.\n *\n * Its own connection rather than the pool's next free one, because a build\n * has to outlive both of a pool's timeouts: the server-side\n * `statement_timeout` (30 s on this framework's pools), which the builder\n * lifts for the session — something it may only do on a connection nobody\n * else is handed until it is reset — and the client-side read timeout, which\n * is lifted here per query.\n */\nexport const poolIndexBuildSession = (pool: Pick<Pool, \"connect\">): OpenIndexBuildSession => async () => {\n const client = await pool.connect();\n return {\n dedicated: true,\n async query<T>(text: string): Promise<{ rows: T[] }> {\n const config: QueryConfig & { query_timeout: number } = { text, query_timeout: INDEX_BUILD_READ_TIMEOUT_MS };\n const result = await client.query<T & QueryResultRow>(config);\n return { rows: result.rows };\n },\n release: () => client.release()\n };\n};\n\nexport interface DeferredIndexOptions extends BootLockOptions {\n /** Progress, one line per index. Failures are also `logger.warn`ed. */\n log?: (message: string) => void;\n /** Checked between indexes; once true, nothing further is started. */\n cancelled?: () => boolean;\n}\n\nexport interface DeferredIndexOutcome {\n /** `schema.index` this run built. */\n built: string[];\n /** Left alone, and why — already valid, or another instance is on it. */\n skipped: { index: string; reason: string }[];\n /** Not built. Logged; the next boot plans them again. */\n failed: { index: string; error: string }[];\n}\n\n/** What the catalogue says about one index name. `valid` is null when it does not exist. */\ninterface IndexState {\n valid: boolean | null;\n /** Another backend is running `CREATE INDEX` on the index's table right now. */\n building: boolean;\n}\n\n/** `42P01 undefined_table` — `pg_stat_progress_create_index` before Postgres 12. */\nconst isUndefinedTable = (err: unknown): boolean => extractPgError(err)?.code === \"42P01\";\n\n/** `25001 active_sql_transaction` — `CONCURRENTLY` on a connection inside a transaction block. */\nconst isTransactionBlockRefusal = (err: unknown): boolean => extractPgError(err)?.code === \"25001\";\n\n/**\n * The rows themselves violate a UNIQUE index being built: `23505` naming the\n * index (a lost catalogue race names a `pg_` index instead). Building it again\n * would only fail again, so it is not retried.\n */\nconst isDuplicateRows = (err: unknown, index: DeferredIndexBuild): boolean => {\n const pg = extractPgError(err);\n return pg?.code === \"23505\" && (pg.constraint === index.indexName || /could not create unique index/i.test(pg.message));\n};\n\nconst CONCURRENTLY_RE = /\\sCONCURRENTLY\\s/i;\n\n/** Why an index is skipped while a build of another index holds its table. */\nexport const TABLE_BUSY_REASON = \"another index on the table is being built\";\n\n/**\n * Run a `CONCURRENTLY` statement — or its plain form, when the connection\n * turns out to be inside a transaction block. Only a shared handle can be: an\n * application that builds its own adapter may hand over a connection already\n * in one. The plain form is always accepted and takes a write lock for the\n * build, which is the cost of getting the index at all — the same fallback the\n * ensure's own index actions have.\n */\nasync function runConcurrently(session: IndexBuildSession, sql: string, qualified: string): Promise<void> {\n try {\n await session.query(sql);\n } catch (err) {\n if (!isTransactionBlockRefusal(err) || !CONCURRENTLY_RE.test(sql)) throw err;\n logger.warn(\n `[schema] ${qualified}: this connection is inside a transaction, so the index is built with a write ` +\n \"lock rather than concurrently. Writes to the table block until it finishes.\"\n );\n await session.query(sql.replace(CONCURRENTLY_RE, \" \"));\n }\n}\n\nasync function readIndexState(session: IndexBuildSession, index: DeferredIndexBuild): Promise<IndexState> {\n const schema = quoteSqlLiteral(index.schema);\n const name = quoteSqlLiteral(index.indexName);\n const { rows } = await session.query<{ valid: boolean | null }>(\n `SELECT i.indisvalid AS valid\n FROM pg_index i\n JOIN pg_class c ON c.oid = i.indexrelid\n JOIN pg_namespace n ON n.oid = c.relnamespace\n WHERE n.nspname = ${schema} AND c.relname = ${name}`\n );\n const valid = rows.length === 0 ? null : rows[0].valid === true;\n if (valid !== false) return { valid, building: false };\n // Invalid is also what an index looks like *while* it is being built\n // concurrently, so before treating it as a husk, ask whether anyone is\n // building on that table now — another instance on a shared handle, or a\n // person with psql. Our own backend is excluded: this session runs one\n // statement at a time, so a row for it is a stale read. With no progress\n // view (Postgres < 12) the answer is \"no\", and dropping an in-flight\n // build is no worse than the husk it would otherwise leave.\n return { valid, building: await tableIsBeingIndexed(session, index) };\n}\n\n/**\n * Wait, within the boot's lock budget, until the table's lock is free.\n *\n * A probe rather than a `lock_timeout` on the build itself: the build waits on\n * lock *queues* in three phases, and only the first — getting the table — is\n * a wait worth bounding. The other two wait for transactions that were\n * already running, which block nobody while they run and must simply be let\n * finish. `true` when the table was free (or cannot be probed on this\n * handle), `false` when something held it for the whole budget.\n */\nasync function tableLockIsFree(\n session: IndexBuildSession,\n index: DeferredIndexBuild,\n options: DeferredIndexOptions\n): Promise<boolean> {\n const lock = resolveBootLockOptions(options);\n const probe = atomicBatch(\n [`LOCK TABLE \"${index.schema}\".\"${index.table}\" IN SHARE UPDATE EXCLUSIVE MODE`],\n lock.lockTimeoutMs\n );\n const deadline = Date.now() + (options.retryBudgetMs ?? BOOT_LOCK_RETRY_BUDGET_MS);\n for (let round = 1; ; round++) {\n try {\n await session.query(probe);\n return true;\n } catch (err) {\n // A handle that speaks only the extended protocol cannot send the\n // probe. Not knowing is not a reason to skip the index.\n if (isMultiStatementRefusal(err)) return true;\n if (!isLockTimeout(err)) throw err;\n if (round >= lock.attempts || Date.now() >= deadline) return false;\n await sleep(Math.min(lock.backoffMs(round), Math.max(0, deadline - Date.now())));\n }\n }\n}\n\n/** Whether another backend is running `CREATE INDEX` on the index's table right now. */\nasync function tableIsBeingIndexed(session: IndexBuildSession, index: DeferredIndexBuild): Promise<boolean> {\n const table = quoteSqlLiteral(`\"${index.schema}\".\"${index.table}\"`);\n try {\n const { rows } = await session.query<{ building: boolean }>(\n `SELECT EXISTS (\n SELECT 1 FROM pg_stat_progress_create_index\n WHERE relid = to_regclass(${table}) AND pid <> pg_backend_pid()\n ) AS building`\n );\n return rows[0]?.building === true;\n } catch (err) {\n if (isUndefinedTable(err)) return false;\n throw err;\n }\n}\n\n/**\n * The advisory-lock key one index is claimed under, across every instance.\n *\n * Without a claim, instances booting together all issue the same\n * `CREATE INDEX CONCURRENTLY`: one wins, the others skip by name — reporting\n * success while the winner is still building — or wait on each other's\n * transactions until Postgres breaks the deadlock by cancelling one, which\n * leaves a husk. With it, one instance builds and the rest step aside.\n */\nconst claimKey = (index: DeferredIndexBuild): string =>\n `hashtext(${quoteSqlLiteral(`rebase_fk_index:${index.schema}.${index.indexName}`)})`;\n\n/**\n * Build one index under its claim — session-level, so only on a connection\n * of our own. On a shared handle the claim could be taken on one backend and\n * released on another, so there is none, and concurrent instances fall back\n * on the state checks below.\n */\nasync function buildClaimed(\n session: IndexBuildSession,\n index: DeferredIndexBuild,\n options: DeferredIndexOptions,\n outcome: DeferredIndexOutcome\n): Promise<void> {\n if (!session.dedicated) return buildOne(session, index, options, outcome);\n const { rows } = await session.query<{ claimed: boolean }>(`SELECT pg_try_advisory_lock(${claimKey(index)}) AS claimed`);\n if (rows[0]?.claimed !== true) {\n outcome.skipped.push({ index: `${index.schema}.${index.indexName}`, reason: \"another instance is building it\" });\n options.log?.(`${index.schema}.${index.indexName}: another instance is building it — leaving it to that one`);\n return;\n }\n try {\n await buildOne(session, index, options, outcome);\n } finally {\n await session.query(`SELECT pg_advisory_unlock(${claimKey(index)})`);\n }\n}\n\n/** One index, from whatever state it is in to valid — or a reason it is not. */\nasync function buildOne(\n session: IndexBuildSession,\n index: DeferredIndexBuild,\n options: DeferredIndexOptions,\n outcome: DeferredIndexOutcome\n): Promise<void> {\n const qualified = `${index.schema}.${index.indexName}`;\n const target = `\"${index.schema}\".\"${index.indexName}\"`;\n const say = (message: string): void => options.log?.(message);\n\n // Two attempts: the build, and one rebuild if the first leaves a husk.\n for (let attempt = 1; attempt <= 2; attempt++) {\n const before = await readIndexState(session, index);\n if (before.valid === true) {\n if (attempt === 1) outcome.skipped.push({ index: qualified, reason: \"already built\" });\n else outcome.built.push(qualified);\n return;\n }\n if (before.building) {\n outcome.skipped.push({ index: qualified, reason: \"another instance is building it\" });\n say(`${qualified}: another instance is building it — leaving it to that one`);\n return;\n }\n if (!await tableLockIsFree(session, index, options)) {\n // Another instance building a different index on the same table\n // holds exactly this lock, for as long as its build takes. That\n // one builds this index next; nothing is wrong.\n if (await tableIsBeingIndexed(session, index)) {\n outcome.skipped.push({ index: qualified, reason: TABLE_BUSY_REASON });\n say(`${qualified}: another index on \"${index.schema}\".\"${index.table}\" is being built — leaving it for now`);\n return;\n }\n outcome.failed.push({\n index: qualified,\n error: `\"${index.schema}\".\"${index.table}\" stayed locked by another session for the whole lock ` +\n \"budget (an index build, a VACUUM, an ALTER TABLE). Nothing was changed; the next boot tries again.\"\n });\n return;\n }\n if (before.valid === false) {\n say(`${qualified}: an earlier build left it INVALID — dropping it to build it again`);\n await runConcurrently(session, `DROP INDEX CONCURRENTLY IF EXISTS ${target};`, qualified);\n }\n\n const started = Date.now();\n say(`${qualified}: building on \"${index.schema}\".\"${index.table}\"…`);\n try {\n await runConcurrently(session, index.sql, qualified);\n } catch (err) {\n // Whatever went wrong, what matters is the state it left, and a\n // peer is the usual cause: two instances issuing the same `CREATE`\n // at the same instant, where the loser fails on a catalogue key,\n // or two concurrent builds on one table each waiting for the\n // other's transaction, where Postgres kills one (40P01).\n const after = await readIndexState(session, index).catch(() => undefined);\n if (after?.valid === true || after?.building) {\n outcome.skipped.push({ index: qualified, reason: \"another instance built it\" });\n return;\n }\n // A build that died part-way leaves a husk, which the next round\n // drops and builds again — once. Not when the rows themselves are\n // what a UNIQUE index refuses: that fails again, as long again.\n if (after?.valid === false && attempt === 1 && !isDuplicateRows(err, index)) {\n say(`${qualified}: the build failed (${describe(err)}); retrying once`);\n continue;\n }\n outcome.failed.push({ index: qualified, error: describe(err) });\n return;\n }\n\n const after = await readIndexState(session, index);\n if (after.valid === true) {\n outcome.built.push(qualified);\n say(`${qualified}: built in ${((Date.now() - started) / 1000).toFixed(1)} s`);\n return;\n }\n if (after.building) {\n outcome.skipped.push({ index: qualified, reason: \"another instance is building it\" });\n return;\n }\n if (attempt === 2) {\n outcome.failed.push({ index: qualified, error: \"left INVALID by two builds in a row\" });\n return;\n }\n }\n}\n\nconst describe = (err: unknown): string => {\n const pg = extractPgError(err);\n if (pg) return pg.code ? `${pg.message} [${pg.code}]` : pg.message;\n return err instanceof Error ? err.message : String(err);\n};\n\n/**\n * Build every deferred index, one at a time, on one connection.\n *\n * Never throws for an index: each failure is recorded and the next one is\n * attempted. Throws only when no connection can be had at all.\n */\nexport async function buildDeferredIndexes(\n openSession: OpenIndexBuildSession,\n indexes: readonly DeferredIndexBuild[],\n options: DeferredIndexOptions = {}\n): Promise<DeferredIndexOutcome> {\n const outcome: DeferredIndexOutcome = { built: [], skipped: [], failed: [] };\n if (indexes.length === 0) return outcome;\n for (const index of indexes) {\n assertSafeIdentifier(index.schema, \"schema name\");\n assertSafeIdentifier(index.table, \"table name\");\n assertSafeIdentifier(index.indexName, \"index name\");\n }\n\n const session = await openSession();\n try {\n // Only on a connection of our own — see `IndexBuildSession.dedicated`.\n // `lock_timeout` stays at the session default: the probe above is\n // where the lock budget applies.\n if (session.dedicated) await session.query(\"SET statement_timeout = 0\");\n for (const index of indexes) {\n if (options.cancelled?.()) break;\n try {\n await buildClaimed(session, index, options, outcome);\n } catch (err) {\n outcome.failed.push({ index: `${index.schema}.${index.indexName}`, error: describe(err) });\n }\n }\n } finally {\n try {\n if (session.dedicated) await session.query(\"RESET statement_timeout\");\n } finally {\n await session.release();\n }\n }\n\n for (const failure of outcome.failed) {\n logger.warn(\n `[schema] Index ${failure.index} was not built: ${failure.error}. ` +\n \"Queries it would serve scan the table until it is; the next boot tries again.\"\n );\n }\n return outcome;\n}\n\n/** Every deferred build in this process, chained so they run one at a time. */\nlet queue: Promise<unknown> = Promise.resolve();\n\n/**\n * Start {@link buildDeferredIndexes} later, in the background.\n *\n * `start()` begins it after `delayMs`; the fallback timer begins it after\n * `fallbackMs` if nothing calls `start()` — so a process whose boot never\n * reaches the step that would have started it still builds. Both timers are\n * unref'd: a pending build never keeps a process alive, and a process that\n * exits mid-build leaves a husk the next boot rebuilds.\n */\nexport interface ScheduledIndexBuild {\n /** Begin after the schedule's `delayMs`, instead of waiting for the fallback timer. */\n start(): void;\n /** Start nothing further. An index already building runs to completion. */\n cancel(): void;\n /** Settles when the run ends — `undefined` if it never ran or could not open a connection. */\n done: Promise<DeferredIndexOutcome | undefined>;\n}\n\nexport function scheduleDeferredIndexBuild(\n openSession: OpenIndexBuildSession,\n indexes: readonly DeferredIndexBuild[],\n options: DeferredIndexOptions & { delayMs?: number; fallbackMs?: number } = {}\n): ScheduledIndexBuild {\n let cancelled = false;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let resolveDone!: (outcome: DeferredIndexOutcome | undefined) => void;\n const done = new Promise<DeferredIndexOutcome | undefined>(resolve => { resolveDone = resolve; });\n\n const run = (): void => {\n if (timer) clearTimeout(timer);\n timer = undefined;\n if (cancelled) {\n resolveDone(undefined);\n return;\n }\n const job = queue.then(async () => {\n if (cancelled) return undefined;\n logger.info(\n `[schema] Building ${indexes.length} index(es) in the background, one at a time ` +\n \"(CREATE INDEX CONCURRENTLY — reads and writes are not blocked).\"\n );\n return buildDeferredIndexes(openSession, indexes, { ...options, cancelled: () => cancelled });\n }).catch((err: unknown) => {\n logger.warn(`[schema] Indexes were not built this boot: ${describe(err)}`);\n return undefined;\n });\n queue = job;\n void job.then(resolveDone);\n };\n\n const arm = (ms: number): void => {\n if (timer) clearTimeout(timer);\n timer = setTimeout(run, ms);\n timer.unref?.();\n };\n arm(options.fallbackMs ?? 60_000);\n\n return {\n start: () => {\n if (cancelled || timer === undefined) return;\n arm(options.delayMs ?? 0);\n },\n cancel: () => {\n cancelled = true;\n if (timer) {\n clearTimeout(timer);\n timer = undefined;\n resolveDone(undefined);\n }\n },\n done\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"deferred-index-build-CqaOv1Xx.js","names":[],"sources":["../src/schema/column-type-drift.ts","../src/schema/plan/diff-plan.ts","../src/schema/deferred-index-build.ts"],"sourcesContent":["/**\n * A column whose type in the database is not the type the collection declares.\n *\n * The boot ensure is additive: it creates tables, columns, enum values and\n * indexes, and it never changes an existing column's type. That is the right\n * default — an automatic `ALTER COLUMN … TYPE` against customer data, decided\n * by a process nobody is watching, is not a thing to do quietly.\n *\n * What was wrong is that it happened *silently*. A property's `columnType`\n * changed after the first deploy, the deploy reported success, the column stayed\n * as it was, and the divergence surfaced later as writes failing:\n *\n * Invalid data format in \"listing_observations\":\n * malformed array literal: \"[0,0,0.0755,…]\"\n *\n * ...after a 10,690-row import, from a deployment that had said \"success\". The\n * two ends disagreed about the schema, so no value satisfied both: the declared\n * shape was rejected by Postgres and the Postgres shape was rejected by the\n * API's own validator, first. There is no encoding out of that, only a report.\n *\n * ## Why the comparison is deliberately blunt\n *\n * A drift report that cries wolf is worse than none, because the boot that says\n * it every time is the boot nobody reads. So types are compared by *family*, and\n * a family is only split from another when the difference changes what the\n * column can hold:\n *\n * - every numeric type is one family. `int4`, `numeric` and `float8` all arrive\n * as JavaScript numbers and accept each other's values; a database adopted\n * from an existing schema legitimately has `int4` where `type: \"number\"`\n * generates `NUMERIC`, and that is not news.\n * - every character type is one family, for the same reason: `text`,\n * `varchar(255)` and `char(3)` differ in what they *reject*, not in shape.\n * - `jsonb` and an array are different families, and that is the case this\n * exists for.\n *\n * Anything this module does not recognise — an enum type, a domain, a PostGIS\n * geometry, an extension type it has never met — returns `null` and is compared\n * with nothing. Silence on an unknown type is the deliberate choice: the cost of\n * a missed report is a question somebody asks once, and the cost of a false one\n * is a warning everybody learns to skip.\n */\n\n/** A column whose live type and declared type are in different families. */\nexport interface ColumnTypeDrift {\n /** `schema.table`. */\n table: string;\n /** The column name as it exists in the database. */\n column: string;\n /** What the collection says the column should be, e.g. `JSONB`. */\n declared: string;\n /** What the database actually has, as `udt_name` reports it, e.g. `_numeric`. */\n actual: string;\n}\n\n/**\n * Types that mean the same thing to everything above the driver.\n *\n * Keyed by every spelling either side can produce: `information_schema` reports\n * `udt_name` (`int4`, `_numeric`, `bpchar`), while the generators emit SQL\n * (`INTEGER`, `NUMERIC[]`, `CHAR(3)`). Both are normalised through here.\n */\nconst FAMILIES: Record<string, string> = {\n // Numbers. One family on purpose — see the note above.\n int2: \"number\", smallint: \"number\",\n int4: \"number\", int: \"number\", integer: \"number\",\n int8: \"number\", bigint: \"number\",\n numeric: \"number\", decimal: \"number\",\n float4: \"number\", real: \"number\",\n float8: \"number\", \"double precision\": \"number\",\n money: \"number\",\n\n // Characters. Also one family: the differences are limits, not shapes.\n text: \"text\", varchar: \"text\", \"character varying\": \"text\",\n bpchar: \"text\", char: \"text\", character: \"text\", citext: \"text\",\n\n bool: \"boolean\", boolean: \"boolean\",\n\n json: \"json\",\n jsonb: \"jsonb\",\n\n uuid: \"uuid\",\n bytea: \"bytea\",\n\n date: \"date\",\n time: \"time\", \"time without time zone\": \"time\",\n timetz: \"time\", \"time with time zone\": \"time\",\n timestamp: \"timestamp\", \"timestamp without time zone\": \"timestamp\",\n timestamptz: \"timestamptz\", \"timestamp with time zone\": \"timestamptz\",\n interval: \"interval\",\n\n tsvector: \"tsvector\",\n vector: \"vector\",\n halfvec: \"halfvec\",\n sparsevec: \"sparsevec\"\n};\n\n/**\n * One SQL or `udt_name` spelling reduced to its family, or `null` when this\n * module has no opinion about it.\n *\n * `null` is not a failure. It is the answer for an enum type, a domain, a\n * PostGIS column, a quoted qualified name — everything whose identity is a\n * *name* rather than a shape, where two spellings being different says nothing\n * about whether the values are compatible.\n */\nexport function typeFamily(raw: string | undefined | null): string | null {\n if (!raw) return null;\n let type = raw.trim().toLowerCase();\n if (!type) return null;\n\n // `\"public\".\"posts_status\"` — an enum, identified by name. Not comparable.\n if (type.startsWith(\"\\\"\")) return null;\n\n // `INTEGER GENERATED BY DEFAULT AS IDENTITY` is an `int4` with a default\n // attached; the identity clause is not part of the type.\n const generated = type.indexOf(\" generated \");\n if (generated > 0) type = type.slice(0, generated).trim();\n\n // Arrays, in both spellings: `NUMERIC[]` from the generators, `_numeric`\n // from `udt_name`. Compared element-wise, so `text[]` and `numeric[]` are\n // different and `int4[]` and `numeric[]` are not.\n if (type.endsWith(\"[]\")) {\n const element = typeFamily(type.slice(0, -2));\n return element && `array:${element}`;\n }\n if (type.startsWith(\"_\")) {\n const element = typeFamily(type.slice(1));\n return element && `array:${element}`;\n }\n\n // `VARCHAR(255)`, `VECTOR(384)`, `NUMERIC(10, 2)` — the width is a limit on\n // a family, not a family. A vector whose *dimensions* changed is a separate\n // concern with its own handling in the ensure.\n const paren = type.indexOf(\"(\");\n if (paren > 0) type = type.slice(0, paren).trim();\n\n return FAMILIES[type] ?? null;\n}\n\n/**\n * Do these two spellings describe columns that hold the same kind of value?\n *\n * `true` whenever the answer is not confidently \"no\" — an unrecognised type on\n * either side agrees with everything, because a report needs evidence and an\n * unknown type is the absence of it.\n */\nexport function typesAgree(declared: string, actual: string): boolean {\n const a = typeFamily(declared);\n const b = typeFamily(actual);\n if (a === null || b === null) return true;\n if (a !== b) return false;\n // Same family, and for `numeric` that is not the end of it: the modifier is\n // what makes `NUMERIC(10, 2)` a price and a bare `NUMERIC` a number that\n // keeps whatever the caller sent. A property that declares\n // `precision`/`scale` is asking the database to round, and a column without\n // them silently does not — so the two disagree, and the drift report says\n // so. A declaration with no modifier accepts whatever is there, which is\n // what it always did.\n //\n // Tested on the spelling, not the family: the family is `number` for every\n // numeric type, and an `int4` under a `NUMERIC(10, 2)` stays the adopted\n // schema it was.\n if (!isNumeric(declared) || !isNumeric(actual)) return true;\n const declaredModifier = numericModifier(declared);\n if (declaredModifier === null) return true;\n return declaredModifier === numericModifier(actual);\n}\n\n/** `NUMERIC`, `numeric(10,2)`, `DECIMAL(10, 2)` — the arbitrary-precision type itself. */\nfunction isNumeric(raw: string): boolean {\n return /^\\s*(?:numeric|decimal)\\s*(?:\\(|$)/i.test(raw);\n}\n\n/**\n * `numeric(10,2)` / `NUMERIC(10, 2)` → `10,2`; a bare `numeric` → `null`. A\n * precision alone means a scale of 0, which is how the catalogue reports it:\n * `NUMERIC(10)` → `10,0`.\n */\nfunction numericModifier(raw: string): string | null {\n const match = raw.match(/\\(([^)]*)\\)/);\n if (!match) return null;\n const [precision, scale = \"0\"] = match[1].split(\",\").map(part => part.trim());\n return `${precision},${scale}`;\n}\n\n/**\n * The sentence a boot log says about one drifted column.\n *\n * Written to be actionable in the place it is read, which is a runtime log with\n * no way to run a migration from it: it names both types, says which surface\n * will break first, and gives the statement that resolves it.\n */\nexport function columnTypeDriftMessage(drift: ColumnTypeDrift): string {\n const [schema, table] = drift.table.split(\".\");\n return (\n `Column \"${drift.table}\".\"${drift.column}\" is ${drift.actual} in the database, but this ` +\n `collection declares ${drift.declared}. Boot does not change an existing column's type, so ` +\n \"the database keeps what it has and writes that match the declaration will be rejected by \" +\n \"Postgres. Change it deliberately, when you can watch it:\\n\" +\n ` ALTER TABLE \"${schema}\".\"${table}\" ALTER COLUMN \"${drift.column}\" ` +\n `TYPE ${drift.declared} USING \"${drift.column}\"::${drift.declared};`\n );\n}\n","/**\n * The additive statements that bring a database up to a {@link SchemaPlan}.\n *\n * ## Why additive-only, forever\n *\n * This runs unattended, at boot, against a database with customers' data in it,\n * with no human reading a diff. So it may only ever do things that cannot lose\n * data: create a missing table, add a missing column, create a missing enum\n * type, add an enum value, create an index.\n *\n * It will **never** drop a table or a column, narrow a type, or alter a\n * constraint at boot. A removed field leaves its column behind; a renamed field\n * looks like an addition and the old column stays. That is the correct trade\n * for an automated path — the alternative is an unattended process that can\n * silently destroy a column. Destructive changes stay a deliberate,\n * human-reviewed migration. Because of that, this is safe to run on every boot,\n * and re-running it is a no-op.\n *\n * ## What changed when the planner arrived\n *\n * This used to read `Property` itself — its own reading of the type, of\n * `validation`, of relations, of enums — and disagreed with `db push` about all\n * of them. The audit found a required `author_id` that was NOT NULL after a\n * push and nullable after a boot, on the one path with no developer in the\n * loop. Now it reads the same {@link ColumnPlan} the DDL renderer does, so a\n * column's type, default, uniqueness and foreign key are decided once. What is\n * left here is the half that is genuinely this path's own: *what the database\n * already has*, and which of the planned constraints are safe to apply to it.\n */\nimport type { ColumnPlan, SchemaPlan, TablePlan } from \"./types\";\nimport { renderColumnDefinition, renderEnableRls, renderPgType, renderPrimaryKeyConstraint } from \"./render-ddl\";\nimport { quoteSqlLiteral } from \"./plan-schema\";\nimport { SET_UPDATED_AT_FN, triggerStatements } from \"./updated-at-trigger\";\nimport {\n SEARCH_STAMP_PREFIX,\n SEARCH_TEXT_FN,\n SEARCH_UNACCENT_FN,\n searchColumnStamps,\n searchIndexStatements\n} from \"../search-column\";\nimport { collectionIndexStatement, sortIndexSpecs } from \"../collection-index\";\nimport { vectorIndexStatement, type SkippedVectorIndex } from \"../vector-index\";\nimport { typesAgree, type ColumnTypeDrift } from \"../column-type-drift\";\n\n/** Postgres identifiers this module is willing to interpolate. */\nconst SAFE_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_$]*$/;\n\n/**\n * Validation used to run only on the names read back OUT of the catalogue,\n * which is the direction that cannot hurt anyone: those came from Postgres. The\n * names going IN — the schema and table a collection declares — were quoted and\n * concatenated on trust, and quoting is not escaping. A table named\n * `x\" (id int); DROP TABLE users; --` closes the quote and the statement, and\n * the whole thing runs on the owner connection, which is the one connection in\n * the system that is exempt from every RLS policy.\n *\n * That is only a config file on a self-hosted project, where the person writing\n * it could run the SQL directly anyway. It is not only a config file where a\n * collection can be defined over the wire — the live-schema and source editors\n * both do that — and there the caller is an admin on the app, not an operator\n * of the database.\n */\nexport function assertSafeIdentifier(value: string, what: string): string {\n if (!SAFE_IDENTIFIER.test(value)) {\n throw new Error(`Refusing to build SQL with an unsafe ${what}: ${JSON.stringify(value)}`);\n }\n return value;\n}\n\n/** What the database currently has, as the planner needs it. */\nexport interface ExistingSchema {\n /** `schema.table` → set of column names. */\n tables: Map<string, Set<string>>;\n /** `schema.typename` of every enum type that already exists. */\n enums: Set<string>;\n /**\n * `schema.table.constraint` of every constraint that already exists.\n *\n * Optional so a caller that only cares about tables can still build one by\n * hand; absent is read as \"none known\", which at worst re-attempts a\n * constraint that then fails harmlessly as a duplicate.\n */\n constraints?: Set<string>;\n /**\n * `schema.table.column` → that column's comment, for the columns that have\n * one. This is where a generated search column's fingerprint lives.\n */\n columnComments?: Map<string, string>;\n /** `schema.typename` → its labels, in `enumsortorder`. */\n enumValues?: Map<string, string[]>;\n /** `schema.table.column` of every NOT NULL column. */\n notNullColumns?: Set<string>;\n /** `schema.table.column` of every column that has a DEFAULT. */\n columnDefaults?: Set<string>;\n /** `schema.table` of every table that holds at least one row. */\n populatedTables?: Set<string>;\n /**\n * `schema.table.column` → the type Postgres reports, as `udt_name` (plus a\n * modifier where one distinguishes two columns of the same family, e.g.\n * `numeric(10,2)`).\n */\n columnTypes?: Map<string, string>;\n /** `schema.table.trigger` of every trigger that already exists. */\n triggers?: Set<string>;\n /**\n * `schema.name` of every index (any relation of kind `i`/`I`) that already\n * exists. Absent means unknown, and every index is planned as before —\n * `IF NOT EXISTS` makes that correct, just not free.\n */\n indexes?: Set<string>;\n /**\n * `schema.name` of every index whose `pg_index.indisvalid` is false — the\n * husk a `CREATE INDEX CONCURRENTLY` leaves behind when it is cancelled,\n * times out, or loses a race with a peer. Such an index is in\n * {@link indexes} too, maintained on every write and used by no query, and\n * `IF NOT EXISTS` skips it by name forever. Absent means unknown.\n */\n invalidIndexes?: Set<string>;\n /**\n * Names of the extensions installed in the database. `CREATE EXTENSION IF\n * NOT EXISTS` skips by name whatever schema the extension is in, so the name\n * is the whole test. Absent means unknown.\n */\n extensions?: Set<string>;\n /**\n * {@link HELPER_FUNCTIONS} as they are in the catalogue: `schema.name(args)`\n * → {@link describeHelperFunction} of what `CREATE OR REPLACE` would set.\n * Absent means unknown, and every helper is re-created as before.\n */\n functions?: Map<string, string>;\n}\n\n/**\n * The functions the plan creates with `CREATE OR REPLACE`, which the catalogue\n * reader looks up so a boot that finds them unchanged plans nothing.\n *\n * A helper missing from this list is not wrong, only re-created on every boot,\n * which is what all of them were before.\n */\nexport const HELPER_FUNCTIONS: readonly string[] = [SEARCH_TEXT_FN, SEARCH_UNACCENT_FN, SET_UPDATED_AT_FN];\n\n/**\n * Everything `CREATE OR REPLACE FUNCTION` sets on a function, as one string.\n *\n * Built from the catalogue by the reader and from the statement by\n * {@link parseHelperFunction}, so the two meet in one shape. It covers every\n * attribute a `CREATE OR REPLACE` resets — body, language, volatility,\n * strictness, parallel safety, security, leakproofness, cost and `SET` options —\n * so a function someone altered by hand (`ALTER FUNCTION … VOLATILE`) still\n * compares unequal and is re-created, exactly as the unconditional statement\n * used to re-create it.\n */\nexport interface HelperFunctionFacts {\n returns: string;\n language: string;\n /** `provolatile`: `i`mmutable, `s`table, `v`olatile. */\n volatility: string;\n strict: boolean;\n /** `proparallel`: `s`afe, `r`estricted, `u`nsafe. */\n parallel: string;\n securityDefiner: boolean;\n leakproof: boolean;\n cost: number;\n /** Whether the function carries any `SET` options (`proconfig`). */\n hasConfig: boolean;\n /** `prosrc`: the text between the dollar quotes, exactly. */\n body: string;\n}\n\nexport const helperFunctionKey = (schema: string, name: string, args: string): string =>\n `${schema}.${name}(${args.replace(/\\s+/g, \" \").trim().toLowerCase()})`;\n\nexport const describeHelperFunction = (facts: HelperFunctionFacts): string =>\n JSON.stringify([\n facts.returns.toLowerCase(), facts.language.toLowerCase(), facts.volatility, facts.strict, facts.parallel,\n facts.securityDefiner, facts.leakproof, Number(facts.cost), facts.hasConfig, facts.body\n ]);\n\nconst HELPER_FUNCTION_RE = new RegExp(\n \"^\\\\s*CREATE OR REPLACE FUNCTION\\\\s+([A-Za-z_]\\\\w*)\\\\.([A-Za-z_]\\\\w*)\\\\(([^)]*)\\\\)\" +\n \"\\\\s+RETURNS\\\\s+([\\\\w\\\\[\\\\]]+)\\\\s+LANGUAGE\\\\s+(\\\\w+)\" +\n \"((?:\\\\s+(?:IMMUTABLE|STABLE|VOLATILE|STRICT|PARALLEL\\\\s+(?:SAFE|RESTRICTED|UNSAFE)))*)\" +\n \"\\\\s+AS\\\\s+\\\\$\\\\$([\\\\s\\\\S]*)\\\\$\\\\$;?\\\\s*$\",\n \"i\"\n);\n\n/**\n * What a helper's `CREATE OR REPLACE FUNCTION` statement would leave in the\n * catalogue, or `undefined` for a statement this does not recognise — which is\n * then planned unconditionally, as every statement was before.\n *\n * Only ever given statements this package renders (`searchHelperFunctions`,\n * `setUpdatedAtFunction`), so a strict pattern is the right tool: anything it\n * does not match exactly falls back to the safe behaviour.\n */\nexport function parseHelperFunction(statement: string): { key: string; definition: string } | undefined {\n const match = HELPER_FUNCTION_RE.exec(statement);\n if (!match) return undefined;\n const [, schema, name, args, returns, language, attributes, body] = match;\n const words = attributes.toUpperCase();\n const parallel = /PARALLEL\\s+SAFE/.test(words) ? \"s\" : /PARALLEL\\s+RESTRICTED/.test(words) ? \"r\" : \"u\";\n const volatility = /\\bIMMUTABLE\\b/.test(words) ? \"i\" : /\\bSTABLE\\b/.test(words) ? \"s\" : \"v\";\n return {\n key: helperFunctionKey(schema, name, args),\n definition: describeHelperFunction({\n returns,\n language,\n volatility,\n strict: /\\bSTRICT\\b/.test(words),\n parallel,\n securityDefiner: false,\n leakproof: false,\n // Postgres's default for every language but C and internal.\n cost: 100,\n hasConfig: false,\n body\n })\n };\n}\n\n/** The name a rendered `CREATE [UNIQUE] INDEX [CONCURRENTLY] IF NOT EXISTS \"<name>\"` creates. */\nconst indexNameOf = (statement: string): string | undefined =>\n /^\\s*CREATE\\s+(?:UNIQUE\\s+)?INDEX\\s+(?:CONCURRENTLY\\s+)?IF\\s+NOT\\s+EXISTS\\s+\"([^\"]+)\"/i.exec(statement)?.[1];\n\n/** The extension a rendered `CREATE EXTENSION IF NOT EXISTS <name> …` installs. */\nconst extensionNameOf = (statement: string): string | undefined =>\n /^\\s*CREATE\\s+EXTENSION\\s+IF\\s+NOT\\s+EXISTS\\s+\"?([\\w-]+)\"?/i.exec(statement)?.[1];\n\n/**\n * How far the planner may go in making the database's constraints match the\n * configuration.\n *\n * - `additive` — the boot default. Columns, tables, indexes and enum values are\n * created; no existing column's constraints are touched. Unattended boots run\n * against customer data with nobody reading a diff, and a database adopted by\n * introspection legitimately carries NOT NULL on columns the generated\n * collection leaves optional (`introspect-db-logic` withholds `required` from\n * a column with a default or a trigger behind it). Converging there would\n * strip real constraints on first boot.\n * - `converge` — the live schema editor. Every statement is planned, shown to\n * the person making the change, and applied only once they confirm it.\n */\nexport type ConstraintPolicy = \"additive\" | \"converge\";\n\nexport interface DiffOptions {\n /** Defaults to `additive`. See {@link ConstraintPolicy}. */\n constraints?: ConstraintPolicy;\n /**\n * Plan every index a table that already exists needs — declared, search\n * and vector, missing or left INVALID under its name — as a\n * {@link DeferredIndexBuild} rather than an action, as the derived\n * foreign-key indexes always are. `ensureCollectionTables` sets it: it\n * hands them to `buildDeferredIndexes`, which builds them on a connection\n * of its own with no `statement_timeout`, and drops and rebuilds a husk.\n *\n * Absent — the live schema editor, which applies `statements` as text —\n * they stay `CREATE INDEX CONCURRENTLY IF NOT EXISTS` actions, as before.\n */\n deferLiveIndexes?: boolean;\n}\n\nexport interface EnsureAction {\n kind: \"create-enum\" | \"create-table\" | \"enable-rls\" | \"add-column\" | \"add-constraint\" | \"rename-column\"\n | \"create-extension\" | \"create-function\" | \"create-index\" | \"comment-column\"\n | \"add-enum-value\" | \"set-not-null\" | \"drop-not-null\" | \"set-default\" | \"create-trigger\";\n /** Qualified target, for logging: `public.posts` or `public.posts.title`. */\n target: string;\n sql: string;\n}\n\n/** A NOT NULL column with no default that no property declares. */\nexport interface OrphanedRequiredColumn {\n /** `schema.table`. */\n table: string;\n /** The column left behind. */\n column: string;\n /** The collection that no longer declares it. */\n slug: string;\n}\n\n/** A constraint the configuration asks for that the planner is not applying. */\nexport interface WithheldConstraint {\n /** `schema.table.column`. */\n target: string;\n kind: \"not-null\" | \"unique\";\n /**\n * Why, in a sentence that names the obstacle rather than the rule. The\n * reader is looking at a column that is nullable when they asked for\n * required, or accepts duplicates when they asked for unique, and needs to\n * know what to do about it.\n */\n reason: string;\n /** What would make it applicable. */\n remedy: string;\n}\n\n/**\n * A generated search column built from a `search` block that has since changed.\n *\n * Reported instead of applied because the two ways to apply it are both worse\n * than stopping. `ALTER COLUMN … SET EXPRESSION` exists only on PG17+ and\n * rewrites the table either way; `DROP COLUMN` + `ADD COLUMN` rewrites it under\n * an ACCESS EXCLUSIVE lock and rebuilds the GIN index. This module runs\n * unattended against live customer data with nobody reading a diff, so a\n * multi-minute outage is not a decision it may take on its own.\n *\n * Not applying it silently is not an option either: a collection that added a\n * field, flipped `unaccent` or raised a weight kept indexing the *old* set\n * forever, and the only symptom was searches returning nothing for content\n * plainly in the row.\n */\nexport interface SearchColumnDrift {\n /** `schema.table`. */\n table: string;\n column: string;\n /** The fingerprint recorded on the column. */\n found: string;\n /** The fingerprint the current `search` block computes. */\n expected: string;\n /** The statements that would rebuild the column, for the operator to run. */\n rebuild: string[];\n}\n\n/** A relation column whose old and new spellings both plausibly apply. */\nexport interface LegacyForeignKey {\n /** `schema.table`. */\n table: string;\n /** The name the current rule derives, and what this plan would create. */\n expected: string;\n /** The name the old rule derived, which the table already has. */\n legacy: string;\n}\n\n/**\n * An index a table that already exists is missing, or holds INVALID — a\n * derived foreign-key index always, and under\n * {@link DiffOptions.deferLiveIndexes} a declared, search or vector one too.\n *\n * Not an {@link EnsureAction}, because it must not hold the boot: on a table\n * with millions of rows `CREATE INDEX CONCURRENTLY` takes minutes, and every\n * minute of it would be a minute the deployment is not serving. The caller\n * builds these after the fact — boot in the background once it serves, every\n * other caller inline after the actions — through `buildDeferredIndexes`. A\n * {@link unique} one is the exception: it is built before the ensure returns.\n *\n * Kept out of {@link EnsurePlan.statements} for the same reason: a caller that\n * applies statements as text (the live schema editor) may run them inside a\n * transaction, which `CONCURRENTLY` refuses.\n */\nexport interface DeferredIndexBuild {\n schema: string;\n /** Bare table name. */\n table: string;\n indexName: string;\n /** `CREATE [UNIQUE] INDEX CONCURRENTLY IF NOT EXISTS …;` */\n sql: string;\n /**\n * The catalogue already holds an INVALID index under this name, which has\n * to be dropped before the build — `IF NOT EXISTS` would skip it.\n */\n invalid: boolean;\n /**\n * A declared `unique: true` index. Not an optimization but the collection\n * promising two rows cannot share a value, so `ensureCollectionTables`\n * builds it before it returns — never after the server is serving — and\n * fails when it cannot, as it did when this was an action. Absent is false.\n */\n unique?: boolean;\n}\n\nexport interface EnsurePlan {\n actions: EnsureAction[];\n /** Every statement, in dependency order. Empty when the schema is current. */\n statements: string[];\n /**\n * Relation columns this plan is about to create where the table already\n * carries the same column under its pre-singularization name.\n *\n * Reported rather than silently handled: the ensure is additive, so it\n * would add `category_id` beside a populated `categorie_id` and the\n * relation would then read the new, empty one. No statement fails, no table\n * is missing, and the only symptom is relations resolving to nothing.\n */\n legacyForeignKeys: LegacyForeignKey[];\n /** @see SearchColumnDrift */\n searchDrift: SearchColumnDrift[];\n /**\n * Generated search columns that exist but carry no fingerprint — created\n * before this check existed, or by `search.sql` on an older CLI. The plan\n * stamps them so the *next* change is detectable.\n */\n searchAdopted: { table: string; column: string }[];\n /**\n * Vector columns this plan is deliberately leaving unindexed, because\n * pgvector cannot build an ANN index that wide. Reported rather than\n * thrown: the column is valid, storable and searchable.\n */\n vectorIndexSkipped: SkippedVectorIndex[];\n /**\n * Constraints the configuration asks for that this plan is not applying,\n * and why. Every one of these was previously withheld in silence.\n */\n withheldConstraints: WithheldConstraint[];\n /**\n * Columns whose type in the database is in a different family from the one\n * the collection declares. Reported and never acted on — this is the one\n * divergence that presents as a *working* deploy.\n */\n columnTypeDrift: ColumnTypeDrift[];\n /**\n * Columns that are NOT NULL, have no default, and that no property declares\n * any more — so every insert through the API is rejected for a field the\n * author cannot name, because they already deleted it. This is what a\n * **rename** looks like to an additive provisioner. Reported, never acted\n * on: dropping the column is the right fix roughly always and destructive\n * exactly once, which is not a decision to take unattended.\n */\n orphanedRequiredColumns: OrphanedRequiredColumn[];\n /** @see DeferredIndexBuild */\n deferredIndexes: DeferredIndexBuild[];\n}\n\n/**\n * Whether a column is part of the table's primary key.\n *\n * Those are created with the table, in step 2, and never added afterwards: a\n * key column missing from a table that already exists cannot be made part of\n * its key by an additive change, so there is nothing safe to plan for one.\n *\n * A relation's entry for a column a declared property owns is not the column —\n * it carries only the constraint — so it is not a key column either, or the\n * key would be created twice.\n */\nconst isKeyColumn = (table: TablePlan, column: ColumnPlan): boolean =>\n !column.columnOwnedByProperty && table.primaryKey.includes(column.column);\n\n/**\n * A declared key whose default is the built-in uuid generator: the default\n * `isId: \"uuid\"` plans. It is the one declared-key default boot sets on an\n * existing table, because it is the one that cannot fail there.\n */\nconst isUuidKeyDefault = (column: ColumnPlan): boolean =>\n column.source.kind === \"property\" &&\n column.default?.kind === \"sql\" &&\n column.default.expression === \"gen_random_uuid()\";\n\n/** Which pass a column belongs to. The order below is the order they run in. */\nconst isScalarProperty = (table: TablePlan, column: ColumnPlan): boolean =>\n (column.source.kind === \"property\" || column.source.kind === \"implicit-id\") && !isKeyColumn(table, column);\n\n/**\n * `ENABLE ROW LEVEL SECURITY` on a table this plan has just created — the\n * statement immediately after its `CREATE TABLE`, never later.\n *\n * The user role every authenticated request runs as holds DML on a new table\n * the moment it exists: the schema's default privileges grant it, and boot's\n * `ensureAppRole` grants it on every table before the policies run. The\n * policies — which also switch RLS on — come after the driver and auth have\n * initialised, so any failure in between left a table with full DML and no row\n * filtering, served by every process in the deployment that does not\n * provision. Born locked, a table denies until its policies arrive, which is\n * the state a failure is allowed to leave behind. Idempotent, so a table a\n * concurrent boot created gets the same statement and nothing changes.\n */\nconst lockAction = (table: TablePlan): EnsureAction => ({\n kind: \"enable-rls\",\n target: table.qualified,\n sql: renderEnableRls(table)\n});\n\nexport function diffPlanAgainstCatalogue(\n plan: SchemaPlan,\n existing: ExistingSchema,\n options: DiffOptions = {}\n): EnsurePlan {\n const constraintPolicy: ConstraintPolicy = options.constraints ?? \"additive\";\n const actions: EnsureAction[] = [];\n const withheldConstraints: WithheldConstraint[] = [];\n const columnTypeDrift: ColumnTypeDrift[] = [];\n const legacyForeignKeys: LegacyForeignKey[] = [];\n const searchDrift: SearchColumnDrift[] = [];\n const searchAdopted: { table: string; column: string }[] = [];\n\n const collectionTables = plan.tables.filter(t => t.kind === \"collection\");\n const junctionTables = plan.tables.filter(t => t.kind === \"junction\");\n\n // 1. Enum types. `CREATE TYPE` has no IF NOT EXISTS, so an existing type is\n // skipped by name rather than guarded in SQL.\n for (const enumPlan of plan.enums) {\n assertSafeIdentifier(enumPlan.schema, \"schema name\");\n if (existing.enums.has(enumPlan.qualified)) {\n // The type is there, but that says nothing about its *values*.\n // Skipping the whole type by name is what made an added enum value\n // vanish: nothing was planned, the boot reported success, and the\n // first row using the value was rejected by a type that had never\n // heard of it. `ADD VALUE` is the one alteration Postgres offers\n // here, it is purely additive, and it is idempotent.\n //\n // `enumValues` absent means the caller built the schema by hand and\n // does not know the values; skip by name as before.\n const current = existing.enumValues?.get(enumPlan.qualified);\n if (!current) continue;\n for (const value of enumPlan.labels) {\n if (current.includes(value)) continue;\n actions.push({\n kind: \"add-enum-value\",\n target: `${enumPlan.qualified}.${value}`,\n // Not inside a transaction with any use of the value:\n // Postgres refuses to read a value added by the transaction\n // still adding it. The applier runs these one statement at a\n // time, which is what makes it legal.\n sql: `ALTER TYPE \"${enumPlan.schema}\".\"${enumPlan.name}\" ADD VALUE IF NOT EXISTS ${quoteSqlLiteral(value)};`\n });\n }\n continue;\n }\n actions.push({\n kind: \"create-enum\",\n target: enumPlan.qualified,\n sql: `CREATE TYPE \"${enumPlan.schema}\".\"${enumPlan.name}\" AS ENUM (${enumPlan.labels.map(quoteSqlLiteral).join(\", \")});`\n });\n }\n\n // 1b. Extensions and helper functions, before the tables and columns that\n // reference them: a generated column's expression is resolved when the\n // column is created, so a table whose search column calls\n // `rebase_search_text` cannot be added before that function exists.\n // Both forms are idempotent, and a boot against a database that already\n // has them plans nothing: each is looked up in the catalogue first. An\n // unconditional `CREATE OR REPLACE` rewrote the function's `pg_proc` row\n // and invalidated every cached plan that calls it, on every replica, on\n // every boot — and was reported as a change applied.\n for (const statement of plan.extensions) {\n const extension = extensionNameOf(statement);\n if (extension && existing.extensions?.has(extension)) continue;\n actions.push({\n kind: \"create-extension\",\n target: statement.replace(/^CREATE EXTENSION IF NOT EXISTS |;$/g, \"\"),\n sql: statement\n });\n }\n for (const statement of plan.functions) {\n const helper = parseHelperFunction(statement);\n if (helper && existing.functions?.get(helper.key) === helper.definition) continue;\n actions.push({ kind: \"create-function\", target: functionTarget(statement), sql: statement });\n }\n\n // 2. Missing tables. Only the key columns are created here; every other\n // column is added by step 3, so a new table and an existing table that\n // gained a field travel the exact same code path. One way to build a\n // column means one way for it to be wrong.\n //\n // A composite key's columns all arrive here, with the one `PRIMARY KEY`\n // constraint naming them: a primary key can only be declared when the\n // table is created, and a table created with the first key column alone\n // would never gain the rest as part of its key.\n const created = new Set<string>();\n for (const table of collectionTables) {\n if (existing.tables.has(table.qualified) || created.has(table.qualified)) continue;\n created.add(table.qualified);\n const schema = assertSafeIdentifier(table.schema, \"schema name\");\n const name = assertSafeIdentifier(table.table, \"table name\");\n const keyColumns = table.columns.filter(c => isKeyColumn(table, c));\n const definitions = keyColumns.length > 0\n ? keyColumns.map(c => `\"${assertSafeIdentifier(c.column, \"column name\")}\" ${renderColumnDefinition(c)}`)\n : ['\"id\" TEXT PRIMARY KEY'];\n if (keyColumns.length > 1) definitions.push(renderPrimaryKeyConstraint(table));\n actions.push({\n kind: \"create-table\",\n target: table.qualified,\n sql: `CREATE TABLE IF NOT EXISTS \"${schema}\".\"${name}\" (${definitions.join(\", \")});`\n });\n actions.push(lockAction(table));\n }\n\n // 2b. Junction tables behind many-to-many relations. No collection declares\n // them, so until they existed an m2m write had nowhere to land and the\n // junction's derived RLS had nothing to attach to. Both columns are\n // created here — unlike a collection table, whose CREATE declares only\n // the identity column — because there is nothing else to them.\n for (const table of junctionTables) {\n if (existing.tables.has(table.qualified) || created.has(table.qualified)) continue;\n created.add(table.qualified);\n const columns = table.columns\n .map(c => `\"${c.column}\" ${renderColumnDefinition(c)}`)\n .join(\", \");\n actions.push({\n kind: \"create-table\",\n target: table.qualified,\n sql: `CREATE TABLE IF NOT EXISTS \"${table.schema}\".\"${table.table}\" (${columns}` +\n `, ${renderPrimaryKeyConstraint(table)});`\n });\n actions.push(lockAction(table));\n }\n\n const addColumn = (table: TablePlan, column: string, definition: string): void => {\n if (existing.tables.get(table.qualified)?.has(column)) return;\n // Same reason as `assertSafeIdentifier` above: a column name reaching\n // here came from a property declaration, which on the editor paths came\n // over the wire. `definition` is built by the renderer from a closed set\n // of type mappings and is not caller text.\n assertSafeIdentifier(column, \"column name\");\n actions.push({\n kind: \"add-column\",\n target: `${table.qualified}.${column}`,\n sql: `ALTER TABLE \"${table.schema}\".\"${table.table}\" ADD COLUMN IF NOT EXISTS \"${column}\" ${definition};`\n });\n };\n\n /**\n * Move a column that is only missing because it was renamed.\n *\n * Returns true when it handled the column, so the caller skips the ordinary\n * ADD. Adding would be the wrong move and a quiet one: the data is in the\n * old column, `ADD COLUMN` creates the new one empty beside it, every\n * statement succeeds, and the relation reads the empty one. A rename is\n * metadata-only in Postgres, keeps the values, and carries the column's\n * indexes and constraints with it.\n */\n const renameLegacyColumn = (table: TablePlan, column: ColumnPlan): boolean => {\n const present = existing.tables.get(table.qualified);\n if (!column.legacyColumn || !present) return false;\n if (present.has(column.column) || !present.has(column.legacyColumn)) return false;\n legacyForeignKeys.push({ table: table.qualified, expected: column.column, legacy: column.legacyColumn });\n actions.push({\n kind: \"rename-column\",\n target: `${table.qualified}.${column.column}`,\n sql: `ALTER TABLE \"${table.schema}\".\"${table.table}\" RENAME COLUMN \"${column.legacyColumn}\" TO \"${column.column}\";`\n });\n return true;\n };\n\n /**\n * A `NOT NULL` is safe exactly when it cannot fail against rows that are\n * already there, and there are three ways to know that:\n *\n * - the table is being created by this plan (no rows yet);\n * - the table exists and is empty;\n * - the column arrives with a DEFAULT, which Postgres backfills into every\n * existing row as part of ADD COLUMN.\n *\n * `populatedTables` absent means the caller does not know, and not knowing\n * has to read as \"assume rows\" — the other direction emits a NOT NULL that\n * is checked against live data and aborts the boot. Written as an explicit\n * `!== undefined` because the optional-chain form quietly says *empty* when\n * the fact is missing, which is the wrong way to be wrong.\n */\n const tableIsEmpty = (table: TablePlan): boolean =>\n existing.populatedTables !== undefined\n && existing.tables.has(table.qualified)\n && !existing.populatedTables.has(table.qualified);\n\n /**\n * The column is there and holds a different kind of value than the\n * collection says it does. Reported, never altered: an unattended `ALTER\n * COLUMN … TYPE` over customer data is not a thing to do quietly, and the\n * *quiet* is what this fixes. A deploy that changed a `columnType` used to\n * report success while the column stayed as it was, and the divergence\n * surfaced later as every write failing. A column whose definition is owned\n * elsewhere (auth, or a generated search column) is not compared: the\n * declaration there is a rendered string rather than a type, and auth's own\n * reconcile owns it.\n */\n const reportTypeDrift = (table: TablePlan, column: ColumnPlan): void => {\n const key = `${table.qualified}.${column.column}`;\n if (existing.tables.get(table.qualified)?.has(column.column) !== true || column.sqlDefinition) return;\n const declaredType = renderPgType(column.type);\n const actualType = existing.columnTypes?.get(key);\n if (actualType && !typesAgree(declaredType, actualType)) {\n columnTypeDrift.push({ table: table.qualified, column: column.column, declared: declaredType, actual: actualType });\n }\n };\n\n /**\n * `SET DEFAULT` on a column that is already there and has none.\n *\n * A DEFAULT binds future writes only, so `SET DEFAULT` cannot fail on data\n * and cannot touch a row that already exists. That makes it the one\n * convergence this path may do unattended — and it has to, because\n * `ADD COLUMN IF NOT EXISTS` is a no-op against an existing column, so a\n * `defaultValue` added to a live collection would otherwise never reach the\n * database at all.\n */\n const planMissingDefault = (table: TablePlan, column: ColumnPlan): void => {\n if (!column.default || column.default.kind === \"identity\" || column.generated) return;\n const key = `${table.qualified}.${column.column}`;\n if (existing.columnDefaults?.has(key)) return;\n const expression = column.default.kind === \"sql\" ? column.default.expression : column.default.sql;\n actions.push({\n kind: \"set-default\",\n target: key,\n sql: `ALTER TABLE \"${table.schema}\".\"${table.table}\" ALTER COLUMN \"${column.column}\" SET DEFAULT ${expression};`\n });\n };\n\n /**\n * Plan one column, with the constraints this database can safely take.\n *\n * The definition comes from the same renderer `schema.sql` uses, minus the\n * constraints that are checked against live rows. That subtraction is the\n * whole of what this path is allowed to decide for itself.\n */\n const planColumn = (table: TablePlan, column: ColumnPlan): void => {\n const key = `${table.qualified}.${column.column}`;\n const columnExists = existing.tables.get(table.qualified)?.has(column.column) === true;\n const fresh = created.has(table.qualified);\n const empty = tableIsEmpty(table);\n const required = !column.nullable;\n const hasDefault = column.default !== undefined;\n\n reportTypeDrift(table, column);\n\n // A table this run is creating has no rows yet, so the constraints\n // `db push` writes are free to apply. On a table that already exists\n // they are checked against live rows, so each is applied only where it\n // cannot fail.\n //\n // NOT NULL needs every existing row to get a value: an empty table, or\n // a default that backfills them.\n //\n // UNIQUE needs every existing row to get a *different* value, and a\n // column being added gets either its default in every row — the same\n // value, so a second row fails — or NULL in every row, and NULLs are\n // distinct. So it holds on an empty table, and on any table when the\n // column arrives with no default. A column that is already there is\n // not touched: whether it carries a unique constraint or index under\n // some other name is not something the catalogue read here says.\n const notNullIsSafe = fresh || empty || hasDefault;\n const uniqueIsSafe = fresh || empty || !hasDefault;\n const applicable: ColumnPlan = {\n ...column,\n unique: column.unique && !columnExists && uniqueIsSafe,\n nullable: column.nullable || !(notNullIsSafe || columnExists)\n };\n if (column.unique && !columnExists && !uniqueIsSafe) {\n withheldConstraints.push({\n target: key,\n kind: \"unique\",\n reason:\n `\"${column.column}\" is unique, but \"${table.qualified}\" ` +\n (existing.populatedTables === undefined ? \"may already hold rows\" : \"already holds rows\") +\n \" and the column's default would give every one of them the same value, so UNIQUE \" +\n \"would fail as it was added.\",\n remedy:\n \"Give each existing row its own value, then add the constraint — \" +\n `\\`ALTER TABLE \"${table.schema}\".\"${table.table}\" ADD UNIQUE (\"${column.column}\")\\` — ` +\n \"or drop the property's default.\"\n });\n }\n if (required && !columnExists && !notNullIsSafe) {\n withheldConstraints.push({\n target: key,\n kind: \"not-null\",\n reason: column.source.kind === \"relation\" || column.source.kind === \"reference\"\n ? `\"${column.column}\" is a required link, but \"${table.qualified}\" already holds rows and ` +\n \"the column has no default to backfill them with, so NOT NULL would be checked \" +\n \"against data that does not have a value yet.\"\n : `\"${column.column}\" is required, but \"${table.qualified}\" already holds rows and the column ` +\n \"has no default to backfill them with, so NOT NULL would be checked \" +\n \"against data that does not have a value yet.\",\n remedy: column.source.kind === \"relation\" || column.source.kind === \"reference\"\n ? \"Backfill the column, then add the constraint — or make the relation optional.\"\n : \"Backfill the column, then add the constraint — or give the property a \" +\n \"default so every existing row gets one.\"\n });\n }\n addColumn(table, column.column, renderColumnDefinition(applicable));\n\n if (!columnExists) return;\n\n planMissingDefault(table, column);\n\n // Two directions on NOT NULL, and they are not equally safe — see\n // `ConstraintPolicy` for why neither runs at an unattended boot.\n if (constraintPolicy !== \"converge\") return;\n const isNotNull = existing.notNullColumns?.has(key) === true;\n if (required && !isNotNull) {\n if (empty) {\n actions.push({\n kind: \"set-not-null\",\n target: key,\n sql: `ALTER TABLE \"${table.schema}\".\"${table.table}\" ALTER COLUMN \"${column.column}\" SET NOT NULL;`\n });\n } else {\n withheldConstraints.push({\n target: key,\n kind: \"not-null\",\n reason:\n `\"${column.column}\" became required, but \"${table.qualified}\" holds rows and any of them ` +\n \"with no value would make SET NOT NULL fail.\",\n remedy:\n \"Backfill the column first — `UPDATE … SET \\\"\" + column.column +\n \"\\\" = … WHERE \\\"\" + column.column + \"\\\" IS NULL` — then apply this again.\"\n });\n }\n }\n if (!required && isNotNull) {\n // Loosening never fails and never loses data. It is here rather\n // than at boot because a database adopted by introspection carries\n // NOT NULL on columns the generated collection deliberately leaves\n // optional, and converging those unasked would drop constraints\n // nobody edited.\n actions.push({\n kind: \"drop-not-null\",\n target: key,\n sql: `ALTER TABLE \"${table.schema}\".\"${table.table}\" ALTER COLUMN \"${column.column}\" DROP NOT NULL;`\n });\n }\n };\n\n // 3. Scalar columns, then the auth columns the collection never mentions.\n // The scaffold's users collection describes 12 of the 14 columns auth\n // reads and writes, so planning only from properties left `is_anonymous`\n // and `tokens_valid_after` to `ensureAuthTablesExist` — which does create\n // them, but only because that function happens to run later in the same\n // boot. Planning them here makes this path self-contained.\n for (const table of collectionTables) {\n assertSafeIdentifier(table.schema, \"schema name\");\n assertSafeIdentifier(table.table, \"table name\");\n for (const column of table.columns) {\n if (!isScalarProperty(table, column)) continue;\n planColumn(table, column);\n }\n // The implicit key of a collection that declares none. It is created\n // with the table, so the only thing to plan is the default a table\n // made before the key had one is missing — without it, nothing can\n // insert a row there: no door sends a key that is not a property.\n //\n // A declared uuid key (`isId: \"uuid\"`) is the same case: no door sends\n // it, and a table adopted by introspection or made by hand may lack\n // its default. Only the built-in generator is converged. A SQL\n // strategy's function may not exist yet, and setting it here would\n // turn an insert-time failure into a boot failure.\n for (const column of table.columns) {\n if (!column.primaryKey) continue;\n if (column.source.kind !== \"implicit-id\" && !isUuidKeyDefault(column)) continue;\n if (existing.tables.get(table.qualified)?.has(column.column) !== true) continue;\n planMissingDefault(table, column);\n }\n for (const column of table.columns) {\n if (column.source.kind !== \"auth\") continue;\n addColumn(table, column.column, renderColumnDefinition(column));\n }\n }\n\n // 3a. The generated search columns.\n //\n // Adding a STORED generated column rewrites the table, which on a large\n // one is not free — but it is the same additive shape as every other\n // column here, and the alternative (leaving it out until someone runs a\n // migration) is a declared `search` block that silently does nothing.\n //\n // Changing one is not additive, and `ADD COLUMN IF NOT EXISTS` is a\n // no-op against a column that is already there — which is why a `search`\n // block that gained a field, flipped `unaccent` or moved a weight used\n // to be inert forever, on every path, with nothing logged. Each column\n // carries a fingerprint of the expression it was built from, and a\n // mismatch is reported rather than applied.\n for (const table of collectionTables) {\n if (!table.search) continue;\n const definitions = new Map(\n table.columns\n .filter(c => c.source.kind === \"search\")\n .map(c => [c.column, renderColumnDefinition(c)] as const)\n );\n for (const stamp of searchColumnStamps(table.search)) {\n const definition = definitions.get(stamp.column)!;\n const exists = existing.tables.get(table.qualified)?.has(stamp.column) === true;\n const recorded = existing.columnComments?.get(`${table.qualified}.${stamp.column}`);\n\n if (exists && recorded?.startsWith(SEARCH_STAMP_PREFIX) && recorded !== stamp.fingerprint) {\n searchDrift.push({\n table: table.qualified,\n column: stamp.column,\n found: recorded,\n expected: stamp.fingerprint,\n rebuild: [\n `ALTER TABLE \"${table.schema}\".\"${table.table}\" DROP COLUMN \"${stamp.column}\";`,\n `ALTER TABLE \"${table.schema}\".\"${table.table}\" ADD COLUMN \"${stamp.column}\" ${definition};`,\n stamp.sql\n ]\n });\n // The old stamp is the only evidence of what the column holds;\n // overwriting it here would erase the drift instead of fixing it.\n continue;\n }\n\n addColumn(table, stamp.column, definition);\n if (exists && recorded === undefined) {\n searchAdopted.push({ table: table.qualified, column: stamp.column });\n }\n if (recorded !== stamp.fingerprint) {\n actions.push({ kind: \"comment-column\", target: `${table.qualified}.${stamp.column}`, sql: stamp.sql });\n }\n }\n }\n\n // 3b. A junction that already existed, but is short a column. One created\n // above already carries both, so re-listing them would log two no-op\n // statements and inflate the count of changes applied.\n for (const table of junctionTables) {\n if (created.has(table.qualified)) continue;\n for (const column of table.columns) {\n if (renameLegacyColumn(table, column)) continue;\n // A payload column (`through.properties`) goes through `planColumn`,\n // the same pass a collection's scalar columns take. The bare\n // `ADD COLUMN <type>` below is right for the two key columns — they\n // are NOT NULL by the composite primary key and have no default —\n // and wrong for everything else: it would drop a `defaultValue`, and\n // it would either omit a NOT NULL the collection declared or apply\n // one to a junction that already holds rows without a value to\n // backfill them. `planColumn` is the one place that decides which of\n // those is safe, and it reports the constraint it withheld.\n if (column.source.kind === \"property\") {\n planColumn(table, column);\n continue;\n }\n // A key column whose type is not its endpoint's key type is a\n // foreign key that can never be added (42804) — said, like any\n // other column's drift, rather than failed on every boot.\n reportTypeDrift(table, column);\n addColumn(table, column.column, renderPgType(column.type));\n }\n }\n\n // 3c. The columns relation and reference properties own.\n for (const table of collectionTables) {\n for (const column of table.columns) {\n if (column.source.kind !== \"relation\" && column.source.kind !== \"reference\") continue;\n // A declared property already emits this column (an explicit\n // `postId` beside the `belongsTo` that uses it); the relation\n // contributes only the constraint, below.\n if (column.columnOwnedByProperty) continue;\n // A reference that is part of a composite key was created with\n // the table, in step 2.\n if (isKeyColumn(table, column)) continue;\n if (renameLegacyColumn(table, column)) continue;\n planColumn(table, column);\n }\n }\n\n // 4. Foreign keys, last: the tables and columns on both ends have to exist\n // first, and a constraint is the one thing here that can fail on data\n // rather than on schema, so nothing else depends on it.\n const knownConstraints = existing.constraints ?? new Set<string>();\n const plannedConstraints = new Set<string>();\n for (const table of [...collectionTables, ...junctionTables]) {\n for (const column of table.columns) {\n const fk = column.foreignKey;\n if (!fk) continue;\n const name = `${fk.schema}.${fk.table}.${fk.constraintName}`;\n if (knownConstraints.has(name) || plannedConstraints.has(name)) continue;\n plannedConstraints.add(name);\n actions.push({ kind: \"add-constraint\", target: name, sql: `${fk.sql};` });\n }\n }\n\n // 5. Indexes, after everything — the column has to exist, and this is the\n // one step that runs against a populated table for real work.\n //\n // CONCURRENTLY: a plain CREATE INDEX takes a lock that blocks writes for\n // the duration of the build, which on a live table is an outage. Each\n // statement here is issued on its own, outside any transaction, which is\n // the condition CONCURRENTLY requires.\n //\n // ...and only on a live table. It is withheld for a table this same plan\n // is creating: there are no rows to build from and no writer to block, so\n // the lock CONCURRENTLY avoids is a lock nobody would have taken. Worth\n // withholding rather than merely harmless, because CONCURRENTLY is the\n // one index form Postgres refuses inside a transaction block.\n const concurrent = (schema: string, table: string): boolean => !created.has(`${schema}.${table}`);\n // An index already in the catalogue under its name is not planned:\n // `IF NOT EXISTS` would skip it by that same name, so the outcome is the\n // same — minus a lock on the table to find that out, and minus a change\n // reported on a database that has none.\n const indexExists = (schema: string, name: string | undefined): boolean =>\n name !== undefined && existing.indexes?.has(`${schema}.${name}`) === true;\n // ...except on a table that already exists, where an index goes to the\n // builder instead (`deferredIndexes`, see `DeferredIndexBuild`): a\n // derived foreign-key index always, and under `deferLiveIndexes` every\n // other kind too. The builder also repairs the one case `IF NOT EXISTS`\n // cannot see — an index that is there by name and INVALID, because a\n // concurrent build of it was cancelled — so it is planned again.\n const deferredIndexes: DeferredIndexBuild[] = [];\n const toBuilder = (schema: string, table: string, derived: boolean): boolean =>\n concurrent(schema, table) && (derived || options.deferLiveIndexes === true);\n const deferIndex = (build: Omit<DeferredIndexBuild, \"invalid\">): void => {\n const invalid = existing.invalidIndexes?.has(`${build.schema}.${build.indexName}`) === true;\n if (indexExists(build.schema, build.indexName) && !invalid) return;\n deferredIndexes.push({ ...build, invalid });\n };\n /** A search or vector statement in its concurrent form, or undefined if it has no plain `CREATE INDEX IF NOT EXISTS`. */\n const concurrentForm = (statement: string): string | undefined => {\n const rewritten = statement.replace(\"CREATE INDEX IF NOT EXISTS\", \"CREATE INDEX CONCURRENTLY IF NOT EXISTS\");\n return rewritten === statement ? undefined : rewritten;\n };\n for (const table of collectionTables) {\n if (!table.search) continue;\n const { schema, table: tableName } = table.search;\n for (const statement of searchIndexStatements(table.search)) {\n const name = indexNameOf(statement);\n const sql = concurrentForm(statement);\n if (name !== undefined && sql !== undefined && toBuilder(schema, tableName, false)) {\n deferIndex({ schema, table: tableName, indexName: name, sql });\n continue;\n }\n if (indexExists(table.schema, name)) continue;\n actions.push({\n kind: \"create-index\",\n target: table.qualified,\n sql: concurrent(table.schema, table.table)\n ? statement.replace(\"CREATE INDEX IF NOT EXISTS\", \"CREATE INDEX CONCURRENTLY IF NOT EXISTS\")\n : statement\n });\n }\n }\n\n // ANN indexes for vector columns, on the same terms. A column too wide\n // for pgvector to index is reported, not planned — silence there would\n // read as \"indexed\" to anyone watching the boot.\n const vectorIndexSkipped: SkippedVectorIndex[] = [];\n for (const table of collectionTables) {\n if (!table.vector) continue;\n for (const spec of table.vector.specs) {\n const sql = concurrentForm(vectorIndexStatement(spec));\n if (sql !== undefined && toBuilder(spec.schema, spec.table, false)) {\n deferIndex({ schema: spec.schema, table: spec.table, indexName: spec.indexName, sql });\n continue;\n }\n if (indexExists(spec.schema, spec.indexName)) continue;\n actions.push({\n kind: \"create-index\",\n target: `${spec.schema}.${spec.table}`,\n sql: concurrent(spec.schema, spec.table)\n ? vectorIndexStatement(spec).replace(\"CREATE INDEX IF NOT EXISTS\", \"CREATE INDEX CONCURRENTLY IF NOT EXISTS\")\n : vectorIndexStatement(spec)\n });\n }\n vectorIndexSkipped.push(...table.vector.skipped);\n }\n\n // Declared indexes, on exactly the same terms. Boot has to emit these,\n // not just `db push`: the managed runtime provisions at boot and never\n // runs a push, so a push-only index would simply not exist there — and\n // nothing would say so. `contracts/derived-names.txt` states the rule\n // (\"Both, or it is a bug\") and the gate enforces it.\n //\n // `concurrently` is a parameter rather than a string replacement on the\n // rendered SQL: the `.replace(…)` above silently does nothing for a\n // UNIQUE index, whose text is `CREATE UNIQUE INDEX …`.\n //\n // The foreign-key indexes the plan derives go through here too, and on a\n // table that already exists they go to the builder whoever plans them\n // (see `toBuilder`). A junction table carries one — its target column —\n // which is why this walks both kinds of table.\n for (const spec of sortIndexSpecs([...collectionTables, ...junctionTables].flatMap(t => t.indexes))) {\n if (toBuilder(spec.schema, spec.table, spec.derived === \"foreign-key\")) {\n deferIndex({\n schema: spec.schema,\n table: spec.table,\n indexName: spec.indexName,\n sql: collectionIndexStatement(spec, { concurrently: true, ifNotExists: true }),\n ...(spec.unique ? { unique: true } : {})\n });\n continue;\n }\n if (indexExists(spec.schema, spec.indexName)) continue;\n actions.push({\n kind: \"create-index\",\n target: `${spec.schema}.${spec.table}`,\n sql: collectionIndexStatement(spec, {\n concurrently: concurrent(spec.schema, spec.table),\n ifNotExists: true\n })\n });\n }\n\n // 6. `autoValue: \"on_update\"` triggers. After the column, and idempotent by\n // DROP-then-CREATE rather than `CREATE OR REPLACE TRIGGER`, which exists\n // only on Postgres 14+.\n for (const table of collectionTables) {\n for (const trigger of table.triggers) {\n // Named after its table and column, so a trigger already in the\n // catalogue is this one — re-issuing it every boot would work and\n // would report a change on a database that has none.\n if (existing.triggers?.has(`${table.qualified}.${trigger.name}`)) continue;\n for (const statement of triggerStatements(trigger)) {\n actions.push({\n kind: \"create-trigger\",\n target: `${table.qualified}.${trigger.name}`,\n sql: statement\n });\n }\n }\n }\n\n // A column the database still requires that no property declares any more.\n //\n // Computed last, over the same live snapshot the rest of the plan read, and\n // only for tables this run did not create — a table created here has exactly\n // the columns the collection asked for, so there is nothing to orphan.\n //\n // Every element of the test matters. NOT NULL, because a nullable leftover\n // accepts a write and is merely untidy. No DEFAULT, because a leftover with\n // one is filled in for you. Not declared, because that is what makes it\n // unreachable — the author has no field to send.\n const orphanedRequiredColumns: OrphanedRequiredColumn[] = [];\n if (existing.notNullColumns && existing.columnDefaults) {\n for (const table of collectionTables) {\n const live = existing.tables.get(table.qualified);\n if (!live || created.has(table.qualified)) continue;\n const declared = new Set<string>();\n for (const column of table.columns) {\n declared.add(column.column);\n if (column.legacyColumn) declared.add(column.legacyColumn);\n }\n for (const column of live) {\n const key = `${table.qualified}.${column}`;\n if (declared.has(column)) continue;\n if (!existing.notNullColumns.has(key)) continue;\n if (existing.columnDefaults.has(key)) continue;\n orphanedRequiredColumns.push({ table: table.qualified, column, slug: table.slug! });\n }\n }\n }\n\n return {\n actions,\n statements: actions.map(a => a.sql),\n legacyForeignKeys,\n searchDrift,\n searchAdopted,\n vectorIndexSkipped,\n withheldConstraints,\n columnTypeDrift,\n orphanedRequiredColumns,\n deferredIndexes\n };\n}\n\n/** What a `CREATE FUNCTION` statement is called, for the action log. */\nconst functionTarget = (statement: string): string => {\n if (statement.includes(\"set_updated_at\")) return SET_UPDATED_AT_FN;\n return statement.includes(\"unaccent\") ? SEARCH_UNACCENT_FN : SEARCH_TEXT_FN;\n};\n","/**\n * Building the indexes a live table is missing — or holds INVALID — without\n * holding anything up: the derived foreign-key indexes, and the declared,\n * search and vector ones the boot-time ensure finds missing on a table that\n * already exists.\n *\n * ## Why not a boot action like an index on a new table\n *\n * A table that already exists may hold millions of rows, and an index over\n * them takes minutes to build. Boot-ensure's actions run before the server\n * listens, so an index planned there is minutes of a deployment not serving —\n * on the first boot after an upgrade that adds one to every `belongsTo` in the\n * project, that is every large table at once. So these are planned apart\n * (`DeferredIndexBuild`) and built here, after the boot, one at a time. A\n * UNIQUE one is the exception: `ensureCollectionTables` builds it here too, but\n * before it returns.\n *\n * ## The build\n *\n * `CREATE INDEX CONCURRENTLY`, outside any transaction (Postgres refuses it\n * inside one), on a connection of its own with `statement_timeout` lifted: the\n * pool's 30 s would cancel any build worth deferring. `CONCURRENTLY` takes\n * `SHARE UPDATE EXCLUSIVE`, which conflicts with no read and no write — only\n * with other DDL, `VACUUM` and `ANALYZE` — so live traffic never queues behind\n * it, however long it runs.\n *\n * The boot's lock budget still applies, to the one wait that matters: getting\n * the table at all. A table something else holds — another instance's build,\n * an `ALTER TABLE`, a `VACUUM FULL` — is probed for `BOOT_LOCK_TIMEOUT_MS` at a\n * time within the boot's retry budget, and left for the next boot when it\n * stays held. Once the build has the table, it waits for the transactions\n * already running against it with no deadline: a timeout there would only turn\n * a slow build into a failed one.\n *\n * ## The husk a failed build leaves\n *\n * A cancelled `CONCURRENTLY` — a timeout, a killed process, a deploy that\n * replaced the pod mid-build, two instances racing on one name — leaves the\n * index behind marked `indisvalid = false`: maintained on every write, used by\n * no query. And `IF NOT EXISTS` is a *name* check, so every later attempt\n * skips it and reports success. This reads `pg_index` instead: an invalid index\n * under our name is dropped (`DROP INDEX CONCURRENTLY`) and built again, once.\n * One that another backend is still building (`pg_stat_progress_create_index`)\n * is left to it.\n *\n * Nothing here is fatal. A failure is logged with the index named and the\n * reason, and the next boot plans the index again.\n */\nimport type { Pool, QueryConfig, QueryResultRow } from \"pg\";\nimport { logger } from \"@rebasepro/server\";\nimport { assertSafeIdentifier, type DeferredIndexBuild } from \"./plan/diff-plan\";\nimport { quoteSqlLiteral } from \"./plan/plan-schema\";\nimport { extractPgError } from \"../utils/pg-error-utils\";\nimport {\n atomicBatch,\n BOOT_LOCK_RETRY_BUDGET_MS,\n isLockTimeout,\n isMultiStatementRefusal,\n resolveBootLockOptions,\n sleep,\n type BootLockOptions\n} from \"./boot-ddl\";\n\n/**\n * A connection the builder may hold for as long as an index takes.\n *\n * `dedicated` says whether consecutive queries reach the same backend. Only\n * then can a session setting be relied on — and be reset afterwards: on a\n * pool, `SET statement_timeout = 0` would land on whichever connection served\n * it and stay there for the next request to inherit.\n */\nexport interface IndexBuildSession {\n query<T = unknown>(sql: string): Promise<{ rows: T[] }>;\n readonly dedicated: boolean;\n /** Hand the connection back. Called once, after the last index. */\n release(): void | Promise<void>;\n}\n\nexport type OpenIndexBuildSession = () => Promise<IndexBuildSession>;\n\n/** A client-side read timeout no index build reaches. A pool's is 60 s. */\nconst INDEX_BUILD_READ_TIMEOUT_MS = 24 * 60 * 60 * 1000;\n\n/** A `pg.Pool` — what `connect()` checks a client out of — and not a `pg.Client`, whose `connect()` opens a socket. */\nexport const isPgPool = (value: unknown): value is Pick<Pool, \"connect\" | \"totalCount\"> =>\n typeof value === \"object\" && value !== null && \"connect\" in value && typeof value.connect === \"function\"\n && \"totalCount\" in value;\n\n/**\n * Sessions checked out of a `pg.Pool`, one connection each.\n *\n * Its own connection rather than the pool's next free one, because a build\n * has to outlive both of a pool's timeouts: the server-side\n * `statement_timeout` (30 s on this framework's pools), which the builder\n * lifts for the session — something it may only do on a connection nobody\n * else is handed until it is reset — and the client-side read timeout, which\n * is lifted here per query.\n */\nexport const poolIndexBuildSession = (pool: Pick<Pool, \"connect\">): OpenIndexBuildSession => async () => {\n const client = await pool.connect();\n return {\n dedicated: true,\n async query<T>(text: string): Promise<{ rows: T[] }> {\n const config: QueryConfig & { query_timeout: number } = { text, query_timeout: INDEX_BUILD_READ_TIMEOUT_MS };\n const result = await client.query<T & QueryResultRow>(config);\n return { rows: result.rows };\n },\n release: () => client.release()\n };\n};\n\nexport interface DeferredIndexOptions extends BootLockOptions {\n /** Progress, one line per index. Failures are also `logger.warn`ed. */\n log?: (message: string) => void;\n /** Checked between indexes; once true, nothing further is started. */\n cancelled?: () => boolean;\n}\n\nexport interface DeferredIndexOutcome {\n /** `schema.index` this run built. */\n built: string[];\n /** Left alone, and why — already valid, or another instance is on it. */\n skipped: { index: string; reason: string }[];\n /** Not built. Logged; the next boot plans them again. */\n failed: { index: string; error: string }[];\n}\n\n/** What the catalogue says about one index name. `valid` is null when it does not exist. */\ninterface IndexState {\n valid: boolean | null;\n /** Another backend is running `CREATE INDEX` on the index's table right now. */\n building: boolean;\n}\n\n/** `42P01 undefined_table` — `pg_stat_progress_create_index` before Postgres 12. */\nconst isUndefinedTable = (err: unknown): boolean => extractPgError(err)?.code === \"42P01\";\n\n/** `25001 active_sql_transaction` — `CONCURRENTLY` on a connection inside a transaction block. */\nconst isTransactionBlockRefusal = (err: unknown): boolean => extractPgError(err)?.code === \"25001\";\n\n/**\n * The rows themselves violate a UNIQUE index being built: `23505` naming the\n * index (a lost catalogue race names a `pg_` index instead). Building it again\n * would only fail again, so it is not retried.\n */\nconst isDuplicateRows = (err: unknown, index: DeferredIndexBuild): boolean => {\n const pg = extractPgError(err);\n return pg?.code === \"23505\" && (pg.constraint === index.indexName || /could not create unique index/i.test(pg.message));\n};\n\nconst CONCURRENTLY_RE = /\\sCONCURRENTLY\\s/i;\n\n/** Why an index is skipped while a build of another index holds its table. */\nexport const TABLE_BUSY_REASON = \"another index on the table is being built\";\n\n/**\n * Run a `CONCURRENTLY` statement — or its plain form, when the connection\n * turns out to be inside a transaction block. Only a shared handle can be: an\n * application that builds its own adapter may hand over a connection already\n * in one. The plain form is always accepted and takes a write lock for the\n * build, which is the cost of getting the index at all — the same fallback the\n * ensure's own index actions have.\n */\nasync function runConcurrently(session: IndexBuildSession, sql: string, qualified: string): Promise<void> {\n try {\n await session.query(sql);\n } catch (err) {\n if (!isTransactionBlockRefusal(err) || !CONCURRENTLY_RE.test(sql)) throw err;\n logger.warn(\n `[schema] ${qualified}: this connection is inside a transaction, so the index is built with a write ` +\n \"lock rather than concurrently. Writes to the table block until it finishes.\"\n );\n await session.query(sql.replace(CONCURRENTLY_RE, \" \"));\n }\n}\n\nasync function readIndexState(session: IndexBuildSession, index: DeferredIndexBuild): Promise<IndexState> {\n const schema = quoteSqlLiteral(index.schema);\n const name = quoteSqlLiteral(index.indexName);\n const { rows } = await session.query<{ valid: boolean | null }>(\n `SELECT i.indisvalid AS valid\n FROM pg_index i\n JOIN pg_class c ON c.oid = i.indexrelid\n JOIN pg_namespace n ON n.oid = c.relnamespace\n WHERE n.nspname = ${schema} AND c.relname = ${name}`\n );\n const valid = rows.length === 0 ? null : rows[0].valid === true;\n if (valid !== false) return { valid, building: false };\n // Invalid is also what an index looks like *while* it is being built\n // concurrently, so before treating it as a husk, ask whether anyone is\n // building on that table now — another instance on a shared handle, or a\n // person with psql. Our own backend is excluded: this session runs one\n // statement at a time, so a row for it is a stale read. With no progress\n // view (Postgres < 12) the answer is \"no\", and dropping an in-flight\n // build is no worse than the husk it would otherwise leave.\n return { valid, building: await tableIsBeingIndexed(session, index) };\n}\n\n/**\n * Wait, within the boot's lock budget, until the table's lock is free.\n *\n * A probe rather than a `lock_timeout` on the build itself: the build waits on\n * lock *queues* in three phases, and only the first — getting the table — is\n * a wait worth bounding. The other two wait for transactions that were\n * already running, which block nobody while they run and must simply be let\n * finish. `true` when the table was free (or cannot be probed on this\n * handle), `false` when something held it for the whole budget.\n */\nasync function tableLockIsFree(\n session: IndexBuildSession,\n index: DeferredIndexBuild,\n options: DeferredIndexOptions\n): Promise<boolean> {\n const lock = resolveBootLockOptions(options);\n const probe = atomicBatch(\n [`LOCK TABLE \"${index.schema}\".\"${index.table}\" IN SHARE UPDATE EXCLUSIVE MODE`],\n lock.lockTimeoutMs\n );\n const deadline = Date.now() + (options.retryBudgetMs ?? BOOT_LOCK_RETRY_BUDGET_MS);\n for (let round = 1; ; round++) {\n try {\n await session.query(probe);\n return true;\n } catch (err) {\n // A handle that speaks only the extended protocol cannot send the\n // probe. Not knowing is not a reason to skip the index.\n if (isMultiStatementRefusal(err)) return true;\n if (!isLockTimeout(err)) throw err;\n if (round >= lock.attempts || Date.now() >= deadline) return false;\n await sleep(Math.min(lock.backoffMs(round), Math.max(0, deadline - Date.now())));\n }\n }\n}\n\n/** Whether another backend is running `CREATE INDEX` on the index's table right now. */\nasync function tableIsBeingIndexed(session: IndexBuildSession, index: DeferredIndexBuild): Promise<boolean> {\n const table = quoteSqlLiteral(`\"${index.schema}\".\"${index.table}\"`);\n try {\n const { rows } = await session.query<{ building: boolean }>(\n `SELECT EXISTS (\n SELECT 1 FROM pg_stat_progress_create_index\n WHERE relid = to_regclass(${table}) AND pid <> pg_backend_pid()\n ) AS building`\n );\n return rows[0]?.building === true;\n } catch (err) {\n if (isUndefinedTable(err)) return false;\n throw err;\n }\n}\n\n/**\n * The advisory-lock key one index is claimed under, across every instance.\n *\n * Without a claim, instances booting together all issue the same\n * `CREATE INDEX CONCURRENTLY`: one wins, the others skip by name — reporting\n * success while the winner is still building — or wait on each other's\n * transactions until Postgres breaks the deadlock by cancelling one, which\n * leaves a husk. With it, one instance builds and the rest step aside.\n */\nconst claimKey = (index: DeferredIndexBuild): string =>\n `hashtext(${quoteSqlLiteral(`rebase_fk_index:${index.schema}.${index.indexName}`)})`;\n\n/**\n * Build one index under its claim — session-level, so only on a connection\n * of our own. On a shared handle the claim could be taken on one backend and\n * released on another, so there is none, and concurrent instances fall back\n * on the state checks below.\n */\nasync function buildClaimed(\n session: IndexBuildSession,\n index: DeferredIndexBuild,\n options: DeferredIndexOptions,\n outcome: DeferredIndexOutcome\n): Promise<void> {\n if (!session.dedicated) return buildOne(session, index, options, outcome);\n const { rows } = await session.query<{ claimed: boolean }>(`SELECT pg_try_advisory_lock(${claimKey(index)}) AS claimed`);\n if (rows[0]?.claimed !== true) {\n outcome.skipped.push({ index: `${index.schema}.${index.indexName}`, reason: \"another instance is building it\" });\n options.log?.(`${index.schema}.${index.indexName}: another instance is building it — leaving it to that one`);\n return;\n }\n try {\n await buildOne(session, index, options, outcome);\n } finally {\n await session.query(`SELECT pg_advisory_unlock(${claimKey(index)})`);\n }\n}\n\n/** One index, from whatever state it is in to valid — or a reason it is not. */\nasync function buildOne(\n session: IndexBuildSession,\n index: DeferredIndexBuild,\n options: DeferredIndexOptions,\n outcome: DeferredIndexOutcome\n): Promise<void> {\n const qualified = `${index.schema}.${index.indexName}`;\n const target = `\"${index.schema}\".\"${index.indexName}\"`;\n const say = (message: string): void => options.log?.(message);\n\n // Two attempts: the build, and one rebuild if the first leaves a husk.\n for (let attempt = 1; attempt <= 2; attempt++) {\n const before = await readIndexState(session, index);\n if (before.valid === true) {\n if (attempt === 1) outcome.skipped.push({ index: qualified, reason: \"already built\" });\n else outcome.built.push(qualified);\n return;\n }\n if (before.building) {\n outcome.skipped.push({ index: qualified, reason: \"another instance is building it\" });\n say(`${qualified}: another instance is building it — leaving it to that one`);\n return;\n }\n if (!await tableLockIsFree(session, index, options)) {\n // Another instance building a different index on the same table\n // holds exactly this lock, for as long as its build takes. That\n // one builds this index next; nothing is wrong.\n if (await tableIsBeingIndexed(session, index)) {\n outcome.skipped.push({ index: qualified, reason: TABLE_BUSY_REASON });\n say(`${qualified}: another index on \"${index.schema}\".\"${index.table}\" is being built — leaving it for now`);\n return;\n }\n outcome.failed.push({\n index: qualified,\n error: `\"${index.schema}\".\"${index.table}\" stayed locked by another session for the whole lock ` +\n \"budget (an index build, a VACUUM, an ALTER TABLE). Nothing was changed; the next boot tries again.\"\n });\n return;\n }\n if (before.valid === false) {\n say(`${qualified}: an earlier build left it INVALID — dropping it to build it again`);\n await runConcurrently(session, `DROP INDEX CONCURRENTLY IF EXISTS ${target};`, qualified);\n }\n\n const started = Date.now();\n say(`${qualified}: building on \"${index.schema}\".\"${index.table}\"…`);\n try {\n await runConcurrently(session, index.sql, qualified);\n } catch (err) {\n // Whatever went wrong, what matters is the state it left, and a\n // peer is the usual cause: two instances issuing the same `CREATE`\n // at the same instant, where the loser fails on a catalogue key,\n // or two concurrent builds on one table each waiting for the\n // other's transaction, where Postgres kills one (40P01).\n const after = await readIndexState(session, index).catch(() => undefined);\n if (after?.valid === true || after?.building) {\n outcome.skipped.push({ index: qualified, reason: \"another instance built it\" });\n return;\n }\n // A build that died part-way leaves a husk, which the next round\n // drops and builds again — once. Not when the rows themselves are\n // what a UNIQUE index refuses: that fails again, as long again.\n if (after?.valid === false && attempt === 1 && !isDuplicateRows(err, index)) {\n say(`${qualified}: the build failed (${describe(err)}); retrying once`);\n continue;\n }\n outcome.failed.push({ index: qualified, error: describe(err) });\n return;\n }\n\n const after = await readIndexState(session, index);\n if (after.valid === true) {\n outcome.built.push(qualified);\n say(`${qualified}: built in ${((Date.now() - started) / 1000).toFixed(1)} s`);\n return;\n }\n if (after.building) {\n outcome.skipped.push({ index: qualified, reason: \"another instance is building it\" });\n return;\n }\n if (attempt === 2) {\n outcome.failed.push({ index: qualified, error: \"left INVALID by two builds in a row\" });\n return;\n }\n }\n}\n\nconst describe = (err: unknown): string => {\n const pg = extractPgError(err);\n if (pg) return pg.code ? `${pg.message} [${pg.code}]` : pg.message;\n return err instanceof Error ? err.message : String(err);\n};\n\n/**\n * Build every deferred index, one at a time, on one connection.\n *\n * Never throws for an index: each failure is recorded and the next one is\n * attempted. Throws only when no connection can be had at all.\n */\nexport async function buildDeferredIndexes(\n openSession: OpenIndexBuildSession,\n indexes: readonly DeferredIndexBuild[],\n options: DeferredIndexOptions = {}\n): Promise<DeferredIndexOutcome> {\n const outcome: DeferredIndexOutcome = { built: [], skipped: [], failed: [] };\n if (indexes.length === 0) return outcome;\n for (const index of indexes) {\n assertSafeIdentifier(index.schema, \"schema name\");\n assertSafeIdentifier(index.table, \"table name\");\n assertSafeIdentifier(index.indexName, \"index name\");\n }\n\n const session = await openSession();\n try {\n // Only on a connection of our own — see `IndexBuildSession.dedicated`.\n // `lock_timeout` stays at the session default: the probe above is\n // where the lock budget applies.\n if (session.dedicated) await session.query(\"SET statement_timeout = 0\");\n for (const index of indexes) {\n if (options.cancelled?.()) break;\n try {\n await buildClaimed(session, index, options, outcome);\n } catch (err) {\n outcome.failed.push({ index: `${index.schema}.${index.indexName}`, error: describe(err) });\n }\n }\n } finally {\n try {\n if (session.dedicated) await session.query(\"RESET statement_timeout\");\n } finally {\n await session.release();\n }\n }\n\n for (const failure of outcome.failed) {\n logger.warn(\n `[schema] Index ${failure.index} was not built: ${failure.error}. ` +\n \"Queries it would serve scan the table until it is; the next boot tries again.\"\n );\n }\n return outcome;\n}\n\n/** Every deferred build in this process, chained so they run one at a time. */\nlet queue: Promise<unknown> = Promise.resolve();\n\n/**\n * Start {@link buildDeferredIndexes} later, in the background.\n *\n * `start()` begins it after `delayMs`; the fallback timer begins it after\n * `fallbackMs` if nothing calls `start()` — so a process whose boot never\n * reaches the step that would have started it still builds. Both timers are\n * unref'd: a pending build never keeps a process alive, and a process that\n * exits mid-build leaves a husk the next boot rebuilds.\n */\nexport interface ScheduledIndexBuild {\n /** Begin after the schedule's `delayMs`, instead of waiting for the fallback timer. */\n start(): void;\n /** Start nothing further. An index already building runs to completion. */\n cancel(): void;\n /** Settles when the run ends — `undefined` if it never ran or could not open a connection. */\n done: Promise<DeferredIndexOutcome | undefined>;\n}\n\nexport function scheduleDeferredIndexBuild(\n openSession: OpenIndexBuildSession,\n indexes: readonly DeferredIndexBuild[],\n options: DeferredIndexOptions & { delayMs?: number; fallbackMs?: number } = {}\n): ScheduledIndexBuild {\n let cancelled = false;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let resolveDone!: (outcome: DeferredIndexOutcome | undefined) => void;\n const done = new Promise<DeferredIndexOutcome | undefined>(resolve => { resolveDone = resolve; });\n\n const run = (): void => {\n if (timer) clearTimeout(timer);\n timer = undefined;\n if (cancelled) {\n resolveDone(undefined);\n return;\n }\n const job = queue.then(async () => {\n if (cancelled) return undefined;\n logger.info(\n `[schema] Building ${indexes.length} index(es) in the background, one at a time ` +\n \"(CREATE INDEX CONCURRENTLY — reads and writes are not blocked).\"\n );\n return buildDeferredIndexes(openSession, indexes, { ...options, cancelled: () => cancelled });\n }).catch((err: unknown) => {\n logger.warn(`[schema] Indexes were not built this boot: ${describe(err)}`);\n return undefined;\n });\n queue = job;\n void job.then(resolveDone);\n };\n\n const arm = (ms: number): void => {\n if (timer) clearTimeout(timer);\n timer = setTimeout(run, ms);\n timer.unref?.();\n };\n arm(options.fallbackMs ?? 60_000);\n\n return {\n start: () => {\n if (cancelled || timer === undefined) return;\n arm(options.delayMs ?? 0);\n },\n cancel: () => {\n cancelled = true;\n if (timer) {\n clearTimeout(timer);\n timer = undefined;\n resolveDone(undefined);\n }\n },\n done\n };\n}\n"],"mappings":"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"}
|