@rebasepro/server-postgres 0.25.0-canary.ge06257e → 0.25.1
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-DNg4txyf.js → BranchService-ClklxyAQ.js} +283 -20
- package/dist/BranchService-ClklxyAQ.js.map +1 -0
- package/dist/backup/backup-cli.d.ts +70 -1
- package/dist/backup/backup-service.d.ts +79 -0
- package/dist/backup/pg-binary.d.ts +2 -2
- package/dist/backup/pg-tools.d.ts +60 -1
- package/dist/backup-cli-gijeXe0u.js +1961 -0
- package/dist/backup-cli-gijeXe0u.js.map +1 -0
- package/dist/{boot-ddl-Dnvpmm_T.js → boot-ddl-DYH95Qeg.js} +3 -3
- package/dist/{boot-ddl-Dnvpmm_T.js.map → boot-ddl-DYH95Qeg.js.map} +1 -1
- package/dist/branch-argv.d.ts +15 -0
- package/dist/branch-prune.d.ts +18 -0
- package/dist/{cli-console-C_ISGwJo.js → cli-console-CQrQ0lAY.js} +19 -13
- package/dist/cli-console-CQrQ0lAY.js.map +1 -0
- package/dist/cli-console.d.ts +4 -3
- package/dist/{cli-errors-mx4sSjB8.js → cli-errors-DiA0XFIS.js} +110 -14
- package/dist/{cli-errors-mx4sSjB8.js.map → cli-errors-DiA0XFIS.js.map} +1 -1
- package/dist/cli-errors.d.ts +26 -1
- 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 +120 -184
- package/dist/cli.js.map +1 -1
- package/dist/collections/validate-relations.d.ts +8 -2
- 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/{rls-bootstrap-sql-Bd-hJk08.js → db-subcommands-BPoHCxJR.js} +455 -3
- package/dist/db-subcommands-BPoHCxJR.js.map +1 -0
- package/dist/db-subcommands.d.ts +11 -0
- package/dist/{deferred-index-build-Ca6OqKyR.js → deferred-index-build-BE0I6sYU.js} +8 -8
- package/dist/{deferred-index-build-Ca6OqKyR.js.map → deferred-index-build-BE0I6sYU.js.map} +1 -1
- package/dist/{doctor-CJyuhV1n.js → doctor-BT--1kq3.js} +107 -11
- package/dist/doctor-BT--1kq3.js.map +1 -0
- package/dist/{ensure-collection-policies-C1ux3V_z.js → ensure-collection-policies-BnGOURpG.js} +60 -11
- package/dist/ensure-collection-policies-BnGOURpG.js.map +1 -0
- package/dist/{ensure-collection-tables-Do-Bvb64.js → ensure-collection-tables-Cet4650m.js} +9 -9
- package/dist/{ensure-collection-tables-Do-Bvb64.js.map → ensure-collection-tables-Cet4650m.js.map} +1 -1
- package/dist/{ensure-tables-Dl_Eh9ow.js → ensure-tables-l_OgFQjN.js} +5 -5
- package/dist/{ensure-tables-Dl_Eh9ow.js.map → ensure-tables-l_OgFQjN.js.map} +1 -1
- package/dist/{enum-labels-kept-D9eTIWCz.js → enum-labels-kept-nheku1pB.js} +3 -3
- package/dist/{enum-labels-kept-D9eTIWCz.js.map → enum-labels-kept-nheku1pB.js.map} +1 -1
- package/dist/{generate-drizzle-schema-Bf8canaR.js → generate-drizzle-schema-DC0BOUpn.js} +3 -3
- package/dist/{generate-drizzle-schema-Bf8canaR.js.map → generate-drizzle-schema-DC0BOUpn.js.map} +1 -1
- package/dist/{generate-drizzle-schema-logic-BaT0H7Gq.js → generate-drizzle-schema-logic-CPzCRD-8.js} +4 -4
- package/dist/{generate-drizzle-schema-logic-BaT0H7Gq.js.map → generate-drizzle-schema-logic-CPzCRD-8.js.map} +1 -1
- package/dist/{generate-files-D50Pxw1G.js → generate-files-BP-4W_gm.js} +6 -6
- package/dist/{generate-files-D50Pxw1G.js.map → generate-files-BP-4W_gm.js.map} +1 -1
- package/dist/{generate-next-step-DCd7yiJ2.js → generate-next-step-DgK5BlBn.js} +22 -14
- package/dist/{generate-next-step-DCd7yiJ2.js.map → generate-next-step-DgK5BlBn.js.map} +1 -1
- package/dist/{generate-postgres-ddl-logic-B4k9djAV.js → generate-postgres-ddl-logic-DPl1oH_z.js} +4 -4
- package/dist/{generate-postgres-ddl-logic-B4k9djAV.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/index.d.ts +1 -0
- package/dist/index.es.js +70 -11
- package/dist/index.es.js.map +1 -1
- package/dist/{introspect-db-logic-j2vYhmdP.js → introspect-db-logic-DgQVzccW.js} +2 -2
- package/dist/{introspect-db-logic-j2vYhmdP.js.map → introspect-db-logic-DgQVzccW.js.map} +1 -1
- package/dist/migrate-policy-stamps-3eqfhes6.js +89 -0
- package/dist/migrate-policy-stamps-3eqfhes6.js.map +1 -0
- package/dist/{open-rebase-D8rytTP0.js → open-rebase-EmqVmS14.js} +78 -1027
- package/dist/open-rebase-EmqVmS14.js.map +1 -0
- package/dist/{pg-error-utils-CdsGYMTm.js → pg-error-utils-CFEnm2m9.js} +11 -4
- package/dist/pg-error-utils-CFEnm2m9.js.map +1 -0
- package/dist/{plan-schema-CsNgK0qi.js → plan-schema-Czf3q4-Q.js} +4 -4
- package/dist/{plan-schema-CsNgK0qi.js.map → plan-schema-Czf3q4-Q.js.map} +1 -1
- package/dist/{policy-drift-CY5P1ZRu.js → policy-drift-X4A-xAzv.js} +3 -3
- package/dist/{policy-drift-CY5P1ZRu.js.map → policy-drift-X4A-xAzv.js.map} +1 -1
- package/dist/pool-disconnects.d.ts +28 -0
- package/dist/{push-index-prebuild-BDFcfRPo.js → push-index-prebuild-COd7i6wm.js} +4 -4
- package/dist/{push-index-prebuild-BDFcfRPo.js.map → push-index-prebuild-COd7i6wm.js.map} +1 -1
- package/dist/{push-legacy-renames-D4bvMWAb.js → push-legacy-renames-CLgYYwPE.js} +4 -4
- package/dist/{push-legacy-renames-D4bvMWAb.js.map → push-legacy-renames-CLgYYwPE.js.map} +1 -1
- package/dist/{push-policy-plan-DTXE6aVa.js → push-policy-plan-D2gpQsdv.js} +23 -7
- package/dist/push-policy-plan-D2gpQsdv.js.map +1 -0
- package/dist/{render-ddl-CrfEe__B.js → render-ddl-4v88y-Gv.js} +4 -4
- package/dist/{render-ddl-CrfEe__B.js.map → render-ddl-4v88y-Gv.js.map} +1 -1
- package/dist/{rls-enforcement-D7P4-ZYX.js → rls-enforcement-DDyNoOSy.js} +2 -2
- package/dist/{rls-enforcement-D7P4-ZYX.js.map → rls-enforcement-DDyNoOSy.js.map} +1 -1
- package/dist/schema/atlas-argv.d.ts +3 -1
- package/dist/schema/doctor-cli.js +5 -5
- package/dist/schema/doctor.d.ts +20 -2
- package/dist/schema/ensure-collection-policies.d.ts +28 -1
- package/dist/schema/generate-drizzle-schema.js +1 -1
- package/dist/schema/generate-next-step.d.ts +20 -8
- 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 +6 -6
- package/dist/schema/introspect-db.js.map +1 -1
- package/dist/schema/migrate-policy-stamps.d.ts +23 -0
- package/dist/schema/push-policy-plan.d.ts +10 -2
- package/dist/services/BranchService.d.ts +85 -0
- package/dist/services/pg-notify-listener.d.ts +2 -0
- package/dist/{source-DB6xJv3p.js → source-fZb92pN8.js} +46 -3
- package/dist/source-fZb92pN8.js.map +1 -0
- 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-DMeXOoyv.js → websocket-BKZwsjmf.js} +18 -5
- package/dist/websocket-BKZwsjmf.js.map +1 -0
- package/package.json +7 -7
- package/dist/BranchService-DNg4txyf.js.map +0 -1
- package/dist/backup-cli-HCEkPcQj.js +0 -556
- package/dist/backup-cli-HCEkPcQj.js.map +0 -1
- package/dist/backup-service-D-UpT7DH.js +0 -857
- package/dist/backup-service-D-UpT7DH.js.map +0 -1
- package/dist/cli-console-C_ISGwJo.js.map +0 -1
- package/dist/cli-flags-DUozBFyK.js +0 -205
- package/dist/cli-flags-DUozBFyK.js.map +0 -1
- package/dist/connection-C938nR3E.js.map +0 -1
- package/dist/databasePoolManager-Yqdw14wY.js.map +0 -1
- package/dist/doctor-CJyuhV1n.js.map +0 -1
- package/dist/ensure-collection-policies-C1ux3V_z.js.map +0 -1
- package/dist/open-rebase-D8rytTP0.js.map +0 -1
- package/dist/pg-error-utils-CdsGYMTm.js.map +0 -1
- package/dist/push-policy-plan-DTXE6aVa.js.map +0 -1
- package/dist/rls-bootstrap-sql-Bd-hJk08.js.map +0 -1
- package/dist/rolldown-runtime-V7rFbMRe.js +0 -39
- package/dist/source-DB6xJv3p.js.map +0 -1
- package/dist/websocket-DMeXOoyv.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"doctor-CJyuhV1n.js","names":[],"sources":["../../codegen/dist/index.es.js","../src/schema/doctor.ts"],"sourcesContent":["import { getDeclaredSubcollections } from \"@rebasepro/types\";\nimport { effectiveAccess, fieldKeyForColumn, findRelation, isRelationRequired, resolveCollectionRelations, sortCollectionsBySlug, toCamelCase, toCollectionAccessor, toPascalCase, toSafeIdentifier } from \"@rebasepro/common\";\n//#region src/utils.ts\n/**\n* Indent a block of text by a given number of spaces\n*/\nfunction indent(text, spaces) {\n\tconst pad = \" \".repeat(spaces);\n\treturn text.split(\"\\n\").map((line) => line.trim() ? pad + line : line).join(\"\\n\");\n}\n//#endregion\n//#region src/generate-types.ts\n/**\n* A schema that cannot be expressed as a valid TypeScript file.\n*\n* Thrown rather than emitted. The generator used to concatenate whatever it was\n* given, so a slug that collided with another one, or that was not an\n* identifier, produced a file that either failed to compile or — worse —\n* compiled while quietly routing one collection to another's slug.\n*/\nvar CodegenError = class extends Error {\n\tconstructor(message) {\n\t\tsuper(message);\n\t\tthis.name = \"CodegenError\";\n\t}\n};\nvar IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;\n/**\n* A property name for the emitted TypeScript: verbatim when it is a valid\n* identifier, quoted otherwise.\n*\n* Every key that reaches the output goes through here. Column names are not\n* required to be identifiers — `\"order\"`, `\"user id\"`, a quoted Postgres\n* identifier — and the previous behaviour of camel-casing them into shape\n* renamed the column in the type while the wire kept the original, so the\n* generated `Row` described fields that did not exist.\n*/\nfunction emitKey(key) {\n\treturn IDENTIFIER.test(key) ? key : JSON.stringify(key);\n}\n/**\n* A string literal, escaped.\n*\n* `\"${value}\"` was the previous form. A value containing a quote closed the\n* literal early, which at best broke the file and at worst let a slug from a\n* remote contract inject top-level statements into a file the developer\n* compiles and bundles.\n*/\nfunction emitString(value) {\n\treturn JSON.stringify(value);\n}\n/** The `id`s of an enum declared as an array, an array of `{ id }`, or an object map. */\nfunction enumIds(raw) {\n\tif (Array.isArray(raw)) return raw.map((entry) => entry && typeof entry === \"object\" ? entry.id : entry);\n\tif (raw && typeof raw === \"object\") return Object.keys(raw);\n\treturn [];\n}\nfunction propertyToTypeScriptType(prop) {\n\tswitch (prop.type) {\n\t\tcase \"string\": {\n\t\t\tconst sp = prop;\n\t\t\tif (sp.enum) {\n\t\t\t\tconst ids = enumIds(sp.enum);\n\t\t\t\tif (ids.length === 0) return \"string\";\n\t\t\t\treturn ids.map((v) => emitString(String(v))).join(\" | \");\n\t\t\t}\n\t\t\treturn \"string\";\n\t\t}\n\t\tcase \"number\": {\n\t\t\tconst np = prop;\n\t\t\tif (np.enum) {\n\t\t\t\tconst ids = enumIds(np.enum);\n\t\t\t\tconst numbers = ids.map(Number);\n\t\t\t\tif (ids.length === 0 || numbers.some((n) => !Number.isFinite(n))) return \"number\";\n\t\t\t\treturn numbers.map((n) => String(n)).join(\" | \");\n\t\t\t}\n\t\t\treturn \"number\";\n\t\t}\n\t\tcase \"boolean\": return \"boolean\";\n\t\tcase \"date\": return \"string\";\n\t\tcase \"geopoint\": return \"{ latitude: number; longitude: number; }\";\n\t\tcase \"reference\": return \"string | number\";\n\t\tcase \"relation\": return \"string | number\";\n\t\tcase \"map\": {\n\t\t\tconst mapProp = prop;\n\t\t\tif (mapProp.properties) return `{ ${Object.entries(mapProp.properties).map(([k, v]) => {\n\t\t\t\tconst child = v;\n\t\t\t\tconst optional = !child.validation?.required;\n\t\t\t\tconst type = propertyToTypeScriptType(child);\n\t\t\t\treturn `${emitKey(k)}${optional ? \"?\" : \"\"}: ${optional ? `${type} | null` : type};`;\n\t\t\t}).join(\" \")} }`;\n\t\t\treturn \"Record<string, unknown>\";\n\t\t}\n\t\tcase \"array\": {\n\t\t\tconst arrProp = prop;\n\t\t\tif (arrProp.of) return `Array<${propertyToTypeScriptType(arrProp.of)}>`;\n\t\t\treturn \"Array<unknown>\";\n\t\t}\n\t\tcase \"vector\": return \"number[]\";\n\t\tcase \"binary\": return \"string\";\n\t\tdefault: return \"unknown\";\n\t}\n}\n/**\n* Unwrap a relation target that arrived as a module namespace rather than the\n* collection itself — `target: () => import(\"./authors\")` is a common slip.\n*/\nfunction resolveTargetCollection(relation) {\n\ttry {\n\t\tlet target = relation.target();\n\t\tif (target && (target.default || target.__esModule)) target = target.default ?? target;\n\t\treturn target;\n\t} catch {\n\t\treturn;\n\t}\n}\n/** The TypeScript type of a foreign key: whatever the target's primary key is. */\nfunction foreignKeyType(relation) {\n\tconst target = resolveTargetCollection(relation);\n\tif (!target?.properties) return \"string | number\";\n\tconst idProp = Object.entries(target.properties).find(([_, p]) => p.isId);\n\tif (!idProp) return \"string | number\";\n\treturn idProp[1].type === \"number\" ? \"number\" : \"string\";\n}\n/**\n* The property's `isId`, on the two property kinds that declare one.\n*\n* `Property` is a union and only `StringProperty` and `NumberProperty` carry\n* `isId`, so it cannot be read off the union directly — which is what\n* `(prop as unknown as Record<string, unknown>).isId` was for, twice. The union\n* discriminates on `type`, so narrowing gets there with no claim at all, and\n* with the declared type (`boolean | \"manual\" | \"uuid\" | string`) intact\n* instead of flattened to `unknown`.\n*/\nfunction declaredIsId(prop) {\n\treturn prop.type === \"string\" || prop.type === \"number\" ? prop.isId : void 0;\n}\n/** Whether a property is the collection's primary key. */\nfunction isPrimaryKey(prop) {\n\treturn Boolean(declaredIsId(prop));\n}\n/**\n* Whether the server assigns this primary key, so a write does not have to.\n* `true` and `\"manual\"` both mean the caller supplies it.\n*/\nfunction isAutoAssignedId(prop) {\n\tconst isId = declaredIsId(prop);\n\treturn Boolean(isId) && isId !== \"manual\" && isId !== true;\n}\n/**\n* The type an *included* relation arrives as: the target's own row, inlined.\n*\n* This is what the read pipeline actually serves — `toRestRow` puts the\n* target's flat columns where the relation was, and the SDK and the HTTP API\n* both go through it. It is deliberately *not* a `{ __type: \"relation\" }`\n* envelope: that shape is the admin's view-model and never reaches a\n* developer's `find()`.\n*\n* Falls back to an open record when the target is not part of this generation\n* run, since there is no `Row` to point at.\n*/\nfunction includedRelationType(relation, accessors) {\n\tconst slug = resolveTargetCollection(relation)?.slug ?? relation.targetSlug;\n\tconst accessor = slug ? accessors.get(slug) : void 0;\n\tconst rowType = accessor ? `Database[${emitString(accessor)}][\"Row\"]` : \"Record<string, unknown>\";\n\tconst pivot = relation.kind === \"manyToMany\" ? pivotType(relation.through.properties, \"read\") : void 0;\n\tconst elementType = pivot ? `${rowType} & { _pivot?: ${pivot} }` : rowType;\n\treturn relation.cardinality === \"many\" ? `Array<${elementType}>` : elementType;\n}\n/**\n* Map every slug to the property name it is reachable under on `client.data`.\n*\n* The accessor is a safe identifier because `client.data.myNotes` is the point\n* of generating this at all, and `collectionsDictionary` maps it back to the\n* slug the wire uses. Two slugs that safe down to the same identifier cannot\n* both have it: the interface would not compile, and the dictionary — an object\n* literal — would silently keep only the last, routing one collection's reads\n* to the other's table. There is no defensible way to pick, so this refuses.\n*/\nfunction buildAccessors(collections) {\n\tconst accessors = /* @__PURE__ */ new Map();\n\tconst bySafeName = /* @__PURE__ */ new Map();\n\tfor (const collection of collections) {\n\t\tconst slug = collection.slug;\n\t\tif (typeof slug !== \"string\" || slug.length === 0) throw new CodegenError(\"A collection has no slug, so it has no name to generate a type for. Every collection needs a unique `slug`.\");\n\t\tconst safe = toSafeIdentifier(slug);\n\t\tif (safe.length === 0) throw new CodegenError(`The slug ${emitString(slug)} has no characters that can form a property name, so it cannot be reached as \\`client.data.<name>\\`. Use a slug containing letters, digits, underscores or dashes.`);\n\t\tconst accessor = toCollectionAccessor(slug);\n\t\tif (accessor !== safe) console.warn(`[codegen] The collection ${emitString(slug)} would be \\`client.data.${safe}\\`, a name the client reserves, so its typed accessor is \\`client.data.${accessor}\\`. \\`client.data.collection(${emitString(slug)})\\` reaches it too.`);\n\t\tconst existing = bySafeName.get(accessor);\n\t\tif (existing !== void 0) throw new CodegenError(`The collections ${emitString(existing)} and ${emitString(slug)} both generate the accessor \"${accessor}\", so only one of them could be reached from the generated client and the other's reads would silently go to the wrong table. Rename one of the slugs.`);\n\t\tbySafeName.set(accessor, slug);\n\t\taccessors.set(slug, accessor);\n\t}\n\treturn accessors;\n}\n/**\n* A column's type on a write: its own type (a date also as a `Date`), or\n* `null` too when it is not required.\n*\n* `Row` has typed a non-required column `T | null` for a while; its write twins\n* did not, so the one way to clear a value — `update(id, { publishedAt: null })`,\n* `{ authorId: null }` to unassign, the edit-form round trip of a row that came\n* back with nulls in it — was a compile error the server would have accepted.\n* A primary key is never null, required or not.\n*/\nfunction writableType(prop, tsType) {\n\tconst written = prop.type === \"date\" ? `${tsType} | Date` : tsType;\n\treturn prop.validation?.required || isPrimaryKey(prop) ? written : `${written} | null`;\n}\n/** One emitted `key: type;` line, already indented. */\nfunction line(key, type, optional) {\n\treturn ` ${emitKey(key)}${optional ? \"?\" : \"\"}: ${type};`;\n}\n/**\n* The keys nobody can reach take off the API surface — in *both* directions.\n*\n* `excludeFromApi` means one thing: the API surface does not mention this\n* property. `Row` already honoured that; `Insert` and `Update` deliberately did\n* not, on the reading that the column is stripped from responses rather than\n* from writes. That left the generated types as the one place a password hash\n* was still named, and it invited a client to send one. The server refuses\n* such a field on a write (`VALIDATION_EXCLUDED_FIELDS`), so nothing generated\n* advertises it.\n*\n* Read through `effectiveAccess`, so the flag and its longhand\n* `access: { read: [], write: [] }` produce the same file. A *role* rule is\n* deliberately not honoured here and `Row` is unchanged by one: a generated type\n* is one shape for every caller, and there is no `Row` that is right for both a\n* reader who holds `hr` and one who does not. The server is the enforcement\n* point; the types describe the surface a caller may name.\n*\n* Keyed by the property name *and* by its column name, the same pair the\n* server's `stripUnreadable` deletes, so a foreign key or a relation addressed\n* under the column name cannot put the property back.\n*/\nfunction excludedApiKeys(properties) {\n\tconst excluded = /* @__PURE__ */ new Set();\n\tfor (const [key, rawProp] of Object.entries(properties)) {\n\t\tconst prop = rawProp;\n\t\tconst access = effectiveAccess(prop);\n\t\tif (access?.read?.length !== 0 || access?.write?.length !== 0) continue;\n\t\texcluded.add(key);\n\t\tif (prop.columnName) excluded.add(prop.columnName);\n\t}\n\treturn excluded;\n}\n/**\n* The keys nobody may write: `access: { write: [] }`, readable or not.\n*\n* Off `Insert` and `Update` for the same reason `excludedApiKeys` takes a\n* hidden property off all three — except that a field everyone can read but\n* nobody can write stays on `Row`. The server refuses one on any write with\n* `VALIDATION_EXCLUDED_FIELDS`, for every caller, `admin` included, so a\n* generated `create({ computed })` compiled and could only ever be a 400.\n*\n* A write rule naming *roles* is not this: some caller can write it, and one\n* generated shape cannot say which.\n*/\nfunction unwritableKeys(properties) {\n\tconst unwritable = /* @__PURE__ */ new Set();\n\tfor (const [key, rawProp] of Object.entries(properties)) {\n\t\tconst prop = rawProp;\n\t\tif (effectiveAccess(prop)?.write?.length !== 0) continue;\n\t\tunwritable.add(key);\n\t\tif (prop.columnName) unwritable.add(prop.columnName);\n\t}\n\treturn unwritable;\n}\nfunction generateTypedefs(input) {\n\tconst collections = sortCollectionsBySlug(input);\n\tconst accessors = buildAccessors(collections);\n\tconst lines = [\n\t\t\"/**\",\n\t\t\" * This file was auto-generated by Rebase.\",\n\t\t\" * Do not make direct changes to the file.\",\n\t\t\" */\",\n\t\t\"\",\n\t\t\"export interface Database {\"\n\t];\n\tfor (const collection of collections) {\n\t\tconst properties = collection.properties ?? {};\n\t\tlet resolvedRelations = {};\n\t\ttry {\n\t\t\tresolvedRelations = resolveCollectionRelations(collection);\n\t\t} catch (e) {\n\t\t\tconsole.warn(`[rebase] Could not resolve the relations of \"${collection.slug}\", so its generated type has no relation fields and none of their foreign-key columns. This is usually a circular import in the collection files — make sure the target is \\`() => otherCollection\\` and not evaluated at module load.\\n ${e instanceof Error ? e.message : String(e)}`);\n\t\t}\n\t\tconst declared = getDeclaredSubcollections(collection);\n\t\tlet subcollectionCount = 0;\n\t\tif (declared) try {\n\t\t\tsubcollectionCount = declared().length;\n\t\t} catch {\n\t\t\tsubcollectionCount = -1;\n\t\t}\n\t\tif (subcollectionCount !== 0) console.warn(`[rebase] \"${collection.slug}\" declares ${subcollectionCount < 0 ? \"\" : `${subcollectionCount} `}subcollection(s), which are not part of the generated Database: they are reached over a nested path (\\`data/${collection.slug}/<id>/<relation>\\`), not as a top-level accessor. Register a subcollection as a collection of its own if you want a typed accessor for it.`);\n\t\tlines.push(` ${emitKey(accessors.get(collection.slug))}: {`);\n\t\tlines.push(\" Row: {\");\n\t\tconst emittedKeys = /* @__PURE__ */ new Set();\n\t\tconst excluded = excludedApiKeys(properties);\n\t\tfor (const key of excluded) emittedKeys.add(key);\n\t\tfor (const [key, rawProp] of Object.entries(properties)) {\n\t\t\tconst prop = rawProp;\n\t\t\tif (prop.type === \"relation\") continue;\n\t\t\tif (excluded.has(key)) continue;\n\t\t\tconst tsType = propertyToTypeScriptType(prop);\n\t\t\tconst isRequired = Boolean(prop.validation?.required) || isPrimaryKey(prop);\n\t\t\tlines.push(line(key, isRequired ? tsType : `${tsType} | null`, !isRequired));\n\t\t\temittedKeys.add(key);\n\t\t}\n\t\tfor (const [relKey, relation] of Object.entries(resolvedRelations)) if (relation.kind === \"belongsTo\" && relation.localKey) {\n\t\t\tconst fkKey = fieldKeyForColumn(collection, relation.localKey);\n\t\t\tif (emittedKeys.has(fkKey)) continue;\n\t\t\tconst fkType = foreignKeyType(relation);\n\t\t\tconst shadowedByInclude = relKey === fkKey;\n\t\t\tconst tsType = shadowedByInclude ? `${fkType} | ${includedRelationType(relation, accessors)}` : fkType;\n\t\t\tconst isRequired = isRelationRequired(collection, relation) && !shadowedByInclude;\n\t\t\tlines.push(line(fkKey, isRequired ? tsType : `${tsType} | null`, !isRequired));\n\t\t\temittedKeys.add(fkKey);\n\t\t}\n\t\tfor (const [key, relation] of Object.entries(resolvedRelations)) {\n\t\t\tif (emittedKeys.has(key)) continue;\n\t\t\tlines.push(line(key, includedRelationType(relation, accessors), true));\n\t\t\temittedKeys.add(key);\n\t\t}\n\t\tfor (const [key, rawProp] of Object.entries(properties)) {\n\t\t\tif (rawProp.type !== \"relation\") continue;\n\t\t\tif (emittedKeys.has(key)) continue;\n\t\t\tlines.push(line(key, \"Record<string, unknown>\", true));\n\t\t\temittedKeys.add(key);\n\t\t}\n\t\tlines.push(\" };\");\n\t\tlines.push(\" Insert: {\");\n\t\tconst notWritable = /* @__PURE__ */ new Set([...excluded, ...unwritableKeys(properties)]);\n\t\temittedKeys.clear();\n\t\tfor (const key of notWritable) emittedKeys.add(key);\n\t\tfor (const [key, rawProp] of Object.entries(properties)) {\n\t\t\tconst prop = rawProp;\n\t\t\tif (prop.type === \"relation\") continue;\n\t\t\tif (notWritable.has(key)) continue;\n\t\t\tconst tsType = propertyToTypeScriptType(prop);\n\t\t\tconst isOptional = !prop.validation?.required || isAutoAssignedId(prop) || Boolean(prop.databaseDefault);\n\t\t\tlines.push(line(key, writableType(prop, tsType), isOptional));\n\t\t\temittedKeys.add(key);\n\t\t}\n\t\temitWritableRelations(lines, collection, properties, resolvedRelations, emittedKeys, false);\n\t\tlines.push(\" };\");\n\t\tlines.push(\" Update: {\");\n\t\temittedKeys.clear();\n\t\tfor (const key of notWritable) emittedKeys.add(key);\n\t\tfor (const [key, rawProp] of Object.entries(properties)) {\n\t\t\tconst prop = rawProp;\n\t\t\tif (prop.type === \"relation\") continue;\n\t\t\tif (isPrimaryKey(prop)) continue;\n\t\t\tif (notWritable.has(key)) continue;\n\t\t\tlines.push(line(key, writableType(prop, propertyToTypeScriptType(prop)), true));\n\t\t\temittedKeys.add(key);\n\t\t}\n\t\temitWritableRelations(lines, collection, properties, resolvedRelations, emittedKeys, true);\n\t\tlines.push(\" };\");\n\t\tif (Object.keys(resolvedRelations).length === 0) lines.push(\" Relations: Record<never, never>;\");\n\t\telse lines.push(\" Relations: {\");\n\t\tfor (const [key, relation] of Object.entries(resolvedRelations)) {\n\t\t\tconst slug = resolveTargetCollection(relation)?.slug ?? relation.targetSlug;\n\t\t\tconst accessor = slug ? accessors.get(slug) : void 0;\n\t\t\tlines.push(` ${emitKey(key)}: ${accessor ? emitString(accessor) : \"never\"};`);\n\t\t}\n\t\tif (Object.keys(resolvedRelations).length > 0) lines.push(\" };\");\n\t\tlines.push(` Slug: ${emitString(collection.slug)};`);\n\t\tlines.push(\" };\");\n\t}\n\tlines.push(\"}\");\n\tlines.push(\"\");\n\tlines.push(\"export type CollectionName = keyof Database;\");\n\tlines.push(\"\");\n\tlines.push(\"export const collectionsDictionary = {\");\n\tfor (const collection of collections) lines.push(` ${emitKey(accessors.get(collection.slug))}: ${emitString(collection.slug)},`);\n\tlines.push(\"} as const;\");\n\tlines.push(\"\");\n\tlines.push(\"export type CollectionsDictionary = typeof collectionsDictionary;\");\n\tlines.push(\"\");\n\tlines.push(...includeHelperLines());\n\treturn lines.join(\"\\n\");\n}\n/**\n* The `include` type machinery, emitted into the generated file.\n*\n* It lives here rather than in `@rebasepro/types` because it is the *graph*\n* that makes it work, and the graph only exists once a project's collections\n* have been generated. `IncludeSpec` in `@rebasepro/types` is deliberately\n* unconstrained — a hand-written row type has no relations in it to check\n* against — and these narrow it for a project that does have them.\n*\n* Two things a caller gets:\n*\n* - `IncludeFor<\"posts\">` — an include whose keys are relations that exist,\n* recursively, so `include: { comments: { include: { authr: true } } }` is a\n* compile error rather than a 400 at runtime.\n* - `RowWith<\"posts\", I>` — the row a read with that include returns, where\n* every included relation is **present** rather than optional. `Row` types a\n* relation as optional because it is absent from every read that did not ask\n* for it; once a read has asked, `row.author.name` should not need a `?.`.\n*\n* The depth bound matches the server's (`MAX_INCLUDE_DEPTH`), and is spelled as\n* a decrementing tuple because TypeScript has no arithmetic — `Prev[3]` is `2`.\n* Without a bound, a self-referencing relation makes the type infinite and the\n* compiler gives up with \"type instantiation is excessively deep\".\n*/\nfunction includeHelperLines() {\n\treturn [\n\t\t\"/** Which collection each of `A`'s relations reaches. */\",\n\t\t\"export type RelationsOf<A extends CollectionName> =\",\n\t\t\" Database[A] extends { Relations: infer R } ? R : Record<string, never>;\",\n\t\t\"\",\n\t\t\"/** The names of `A`'s relations. */\",\n\t\t\"export type RelationKeys<A extends CollectionName> = keyof RelationsOf<A> & string;\",\n\t\t\"\",\n\t\t\"/** The collection a relation reaches, or `never` when it left this project. */\",\n\t\t\"export type RelationTarget<A extends CollectionName, K extends RelationKeys<A>> =\",\n\t\t\" RelationsOf<A>[K] extends CollectionName ? RelationsOf<A>[K] : never;\",\n\t\t\"\",\n\t\t\"/** Counts `IncludeFor` down; TypeScript has no arithmetic. */\",\n\t\t\"type Prev = [never, 0, 1, 2, 3];\",\n\t\t\"\",\n\t\t\"/**\",\n\t\t\" * Per-relation options, minus `include` — which `IncludeFor` supplies at\",\n\t\t\" * the next depth so the nested keys are checked against the *target's*\",\n\t\t\" * relations rather than this collection's.\",\n\t\t\" */\",\n\t\t\"export interface IncludeOptionsFor<A extends CollectionName, K extends RelationKeys<A>, D extends number> {\",\n\t\t\" limit?: number;\",\n\t\t\" where?: Record<string, unknown>;\",\n\t\t\" logical?: unknown;\",\n\t\t\" orderBy?: unknown;\",\n\t\t\" fields?: string[];\",\n\t\t\" include?: RelationTarget<A, K> extends CollectionName\",\n\t\t\" ? IncludeFor<RelationTarget<A, K>, Prev[D]>\",\n\t\t\" : never;\",\n\t\t\"}\",\n\t\t\"\",\n\t\t\"/**\",\n\t\t\" * An `include` for collection `A`: its relation names, dotted paths, or the\",\n\t\t\" * parametrised tree — with every key checked against the relations that\",\n\t\t\" * actually exist, at every level.\",\n\t\t\" */\",\n\t\t\"export type IncludeFor<A extends CollectionName, D extends number = 3> =\",\n\t\t\" D extends 0\",\n\t\t\" ? never\",\n\t\t\" : | readonly (RelationKeys<A> | \\\"*\\\")[]\",\n\t\t\" | { [K in RelationKeys<A>]?: true | IncludeOptionsFor<A, K, D> };\",\n\t\t\"\",\n\t\t\"/** The relation names an include asks for at the top level. */\",\n\t\t\"type IncludedKeys<A extends CollectionName, I> =\",\n\t\t\" I extends readonly (infer K)[]\",\n\t\t\" ? Extract<K, RelationKeys<A>>\",\n\t\t\" : Extract<keyof I, RelationKeys<A>>;\",\n\t\t\"\",\n\t\t\"/**\",\n\t\t\" * The row a read with include `I` returns: `Row`, with every included\",\n\t\t\" * relation made **required**.\",\n\t\t\" *\",\n\t\t\" * `Row` types a relation as optional because it is absent from every read\",\n\t\t\" * that did not name it. Once a read has named it, it is there — and having\",\n\t\t\" * to write `row.author?.name` after asking for the author is the type\",\n\t\t\" * describing a possibility the query already ruled out.\",\n\t\t\" */\",\n\t\t\"export type RowWith<A extends CollectionName, I> =\",\n\t\t\" Database[A][\\\"Row\\\"] & Required<Pick<Database[A][\\\"Row\\\"], IncludedKeys<A, I> & keyof Database[A][\\\"Row\\\"]>>;\",\n\t\t\"\"\n\t];\n}\n/**\n* The two ways a write can name a `belongsTo` target, both of which the server\n* accepts: the foreign key under its own wire name (`{ authorId: 5 }`, which\n* passes through to the `author_id` column untouched) and the relation\n* *property* (`{ author: 5 }`, which the write transformer maps onto that\n* column).\n*\n* Only the first was generated, so the documented and idiomatic write shape was\n* a type error.\n*\n* The second form is emitted under the **property key**, not the resolved\n* relation name, because that is what the transformer keys off: it looks the\n* payload key up in `properties` and only treats it as a relation if what it\n* finds there is one. A relation whose `relationName` differs from its property\n* key is reachable as the property and not as the name, so emitting the name\n* would have offered a key that writes to a column that does not exist.\n*/\nfunction emitWritableRelations(lines, collection, properties, resolvedRelations, emittedKeys, allOptional) {\n\tconst emit = (key, relation) => {\n\t\tif (emittedKeys.has(key)) return;\n\t\tconst required = isRelationRequired(collection, relation);\n\t\tconst type = required ? foreignKeyType(relation) : `${foreignKeyType(relation)} | null`;\n\t\tlines.push(line(key, type, allOptional || !required));\n\t\temittedKeys.add(key);\n\t};\n\tfor (const relation of Object.values(resolvedRelations)) if (relation.kind === \"belongsTo\" && relation.localKey) emit(fieldKeyForColumn(collection, relation.localKey), relation);\n\tfor (const [key, rawProp] of Object.entries(properties)) {\n\t\tif (rawProp.type !== \"relation\") continue;\n\t\tconst relation = findRelation(resolvedRelations, key);\n\t\tif (relation?.kind === \"belongsTo\" && relation.localKey) emit(key, relation);\n\t}\n\tfor (const [key, relation] of Object.entries(resolvedRelations)) {\n\t\tif (relation.cardinality !== \"many\" || !relation.writable) continue;\n\t\tif (emittedKeys.has(key)) continue;\n\t\tconst fk = foreignKeyType(relation);\n\t\tconst pivot = relation.kind === \"manyToMany\" ? pivotType(relation.through.properties, \"write\") : void 0;\n\t\tconst element = `${fk} | { id: ${fk}${pivot ? `; _pivot?: ${pivot}` : \"\"} }`;\n\t\tlines.push(line(key, `Array<${element}>`, true));\n\t\temittedKeys.add(key);\n\t}\n}\n/**\n* The `_pivot` a many-to-many link carries: the junction's payload columns,\n* typed as a row's columns are on a read and as a write's on a write. `undefined`\n* when the junction declares none, so there is no `_pivot` to name.\n*/\nfunction pivotType(pivotProperties, direction) {\n\tconst entries = Object.entries(pivotProperties);\n\tif (entries.length === 0) return void 0;\n\treturn `{ ${entries.map(([key, rawProp]) => {\n\t\tconst prop = rawProp;\n\t\tconst tsType = propertyToTypeScriptType(prop);\n\t\tconst required = Boolean(prop.validation?.required);\n\t\tconst type = direction === \"write\" ? writableType(prop, tsType) : required ? tsType : `${tsType} | null`;\n\t\treturn `${emitKey(key)}${required && direction === \"read\" ? \"\" : \"?\"}: ${type};`;\n\t}).join(\" \")} }`;\n}\n//#endregion\n//#region src/index.ts\nfunction generateSDK(collections, options = {}) {\n\tconst files = [];\n\tfiles.push({\n\t\tpath: \"database.types.ts\",\n\t\tcontent: generateTypedefs(collections)\n\t});\n\tif (options.includeReadme !== false) files.push({\n\t\tpath: \"README.md\",\n\t\tcontent: `# Rebase SDK\n\n> Auto-generated by \\`rebase generate-sdk\\`. Do not edit manually.\n\n## Usage\n\n1. Install the client package:\n \\`\\`\\`bash\n npm install @rebasepro/client\n \\`\\`\\`\n\n2. Initialize with your generated types:\n \\`\\`\\`typescript\n import { createRebaseClient } from '@rebasepro/client';\n import { collectionsDictionary, type Database } from './database.types';\n\n const rebase = createRebaseClient<Database>({\n baseUrl: 'http://localhost:3001',\n // Maps each accessor back to the slug the wire uses. Without it a\n // hyphenated slug is not resolvable from the property name alone.\n collections: collectionsDictionary,\n });\n\n // Property access is the typed surface: rows, filters and sorts are all\n // checked against the generated Database.\n const { data: users } = await rebase.data.users.find();\n console.log(users[0].email); // flat access — no .values wrapper\n \\`\\`\\`\n\n## Field names are the ones the API serves\n\nThe generated \\`Row\\` uses each field's **wire** name — the key it arrives under in\nJSON — and nothing here renames anything.\n\n- **A declared property is its key in the collection.** A property keyed\n \\`createdAt\\` is \\`row.createdAt\\`, whatever \\`columnName\\` says. A column name is\n the name of a different thing: where the value lives, not what the API calls it.\n- **A foreign key derived from a relation is camelCase**, because that is what\n the wire carries. A \\`belongsTo\\` named \\`author\\` gives you \\`row.authorId\\`, not\n the column spelling.\n- **A collection accessor is camelCase too** (\\`my-notes\\` → \\`rebase.data.myNotes\\`),\n which is what \\`collectionsDictionary\\` maps back to the slug.\n A slug whose accessor the client reserves (\\`collection\\`, \\`then\\`, \\`toJSON\\`)\n is generated as \\`<name>Collection\\`: \\`rebase.data.collectionCollection\\`.\n\n\\`where\\` and \\`orderBy\\` are keyed off the same type, so what compiles is what the\nbackend answers to.\n\n## \\`Row\\` vs \\`Insert\\` vs \\`Update\\`\n\n| Type | What it describes |\n|---|---|\n| \\`Row\\` | What a read serves. Nullable columns are \\`T \\\\| null\\`; relations appear only when \\`include\\` names them. |\n| \\`Insert\\` | What \\`create()\\` accepts. Server-assigned ids are optional; a \\`belongsTo\\` target may be named either way (\\`{ author: 5 }\\` or \\`{ authorId: 5 }\\`). |\n| \\`Update\\` | What \\`update()\\` accepts. Everything optional, and the primary key is not settable. |\n\nA property marked \\`excludeFromApi\\` is absent from all three: the API surface\ndoes not mention it, in either direction. The server holds the same line — a\nread never serves the column and a write naming it is refused — so this is a\nguarantee rather than a description, and nothing generated names a password\nhash.\n\nIf you need an untyped escape hatch, \\`rebase.data.collection(slug)\\` still works —\nbut it is generic over \\`Record<string, unknown>\\` and gives up everything above.\n`\n\t});\n\treturn files;\n}\n//#endregion\nexport { CodegenError, generateSDK, generateTypedefs, indent, toCamelCase, toCollectionAccessor, toPascalCase, toSafeIdentifier };\n\n//# sourceMappingURL=index.es.js.map","/**\n * Rebase Schema Doctor — Three-way schema drift detection.\n *\n * Compares:\n * 1. Collection definitions → Generated Drizzle schema (staleness check)\n * 2. Collection definitions → Live PostgreSQL database (structural drift)\n *\n * Run via: rebase doctor\n */\nimport { promises as fsPromises } from \"fs\";\nimport * as fs from \"fs\";\nimport path from \"path\";\nimport { pathToFileURL } from \"url\";\nimport chalk from \"chalk\";\nimport { computeSchemaVersion, CollectionConfig, isPostgresCollectionConfig, Property, NumberProperty, StringProperty, DateProperty, ArrayProperty, MapProperty, RelationProperty, type ResolvedManyToMany, type ResolvedBelongsTo, isManyToMany } from \"@rebasepro/types\";\nimport { generateSchema } from \"./generate-drizzle-schema-logic\";\nimport { compareGeneratedDeclarations, describeDeclarationDifferences } from \"./generated-schema-staleness\";\nimport { columnPgType, planSchema } from \"./plan/plan-schema\";\nimport { renderPgType } from \"./plan/render-ddl\";\nimport type { PgType, SchemaPlan, TablePlan } from \"./plan/types\";\nimport { generateTypedefs } from \"@rebasepro/codegen\";\nimport { getTableName, resolveCollectionRelations, findRelation, relationalCollections, resolveJunctionSpecs } from \"@rebasepro/common\";\nimport { toPostgresIdentifier, toSnakeCase } from \"@rebasepro/utils\";\nimport { loadCollectionsFromDirectory } from \"@rebasepro/server\";\n// The report is CLI output, not application logging — see cli-output.ts.\nimport { out, outError } from \"../cli-output\";\nimport { deepestErrorMessage, diagnoseDbError, parseHostInfo } from \"../cli-errors\";\n\n/**\n * Resolve the SQL column name for a property.\n * Uses the explicit `columnName` when set (e.g. from introspection),\n * falling back to `toSnakeCase(propName)` for manually-authored collections.\n */\nconst resolveColumnName = (propName: string, prop?: Property | null): string => {\n if (prop && \"columnName\" in prop && typeof prop.columnName === \"string\") {\n return prop.columnName;\n }\n return toSnakeCase(propName);\n};\n\n// ── Types ────────────────────────────────────────────────────────────────\n\nexport type IssueSeverity = \"error\" | \"warning\" | \"info\";\n\nexport interface DoctorIssue {\n severity: IssueSeverity;\n category: \"missing_table\" | \"missing_column\" | \"type_mismatch\" | \"missing_constraint\" | \"primary_key_mismatch\" | \"schema_stale\" | \"missing_enum\" | \"enum_value_mismatch\" | \"missing_foreign_key\" | \"orphaned_junction\" | \"sdk_stale\" | \"sdk_not_generated\" | \"sdk_ungeneratable\";\n table?: string;\n column?: string;\n expected?: string;\n actual?: string;\n message: string;\n fix: string;\n}\n\nexport interface DoctorPhase {\n passed: boolean;\n issues: DoctorIssue[];\n /**\n * Why this phase never ran, when it did not.\n *\n * A check that did not happen is a third state, not a passing one. While a\n * skipped phase initialised to `{ passed: true, issues: [] }` it rendered as\n * `✅ Collections → Database: In sync` and counted towards\n * `✓ All schemas are in sync!` — so a project whose connection string was\n * spelled `POSTGRES_URL`, or a CI job that never exported one, got two green\n * ticks and exit 0 against a database with no tables in it.\n */\n skipped?: string;\n /**\n * Why this phase had nothing to compare against, when it had nothing.\n *\n * Distinct from `skipped`, which means \"the check could not run, and that\n * is probably worth fixing\". This one means \"the artifact is optional and\n * you have not asked for it\": no drift is possible, so the run is still a\n * clean bill of health.\n *\n * It exists because the alternative was a contradiction. The typed-SDK\n * phase returned `{ passed: true }` when `generated/sdk/database.types.ts`\n * did not exist, so a fresh project's report read\n * `✅ Collections → SDK Types: In sync` directly above\n * `ℹ Typed SDK not generated (optional).` — one line calling a file\n * synchronised and the next saying it is absent. \"In sync\" is a claim about\n * a comparison, and no comparison happened.\n */\n notApplicable?: string;\n /**\n * The phase was skipped by a fault rather than by a choice.\n *\n * `skipped` covers both: \"no DATABASE_URL, so nothing to compare against\"\n * is a local situation a developer may be perfectly happy with, and \"the\n * DATABASE_URL you set refuses connections\" is not. The report renders them\n * the same way — a check that did not run is a check that did not run — but\n * the exit code must not, or `rebase doctor` in CI goes green against a\n * database it never reached.\n */\n blocked?: boolean;\n}\n\nexport interface DoctorReport {\n collectionsToSchema: DoctorPhase;\n collectionsToSdk: DoctorPhase;\n schemaToDatabase: DoctorPhase;\n summary: { passed: number; skipped: number; notApplicable: number; warnings: number; errors: number };\n}\n\n// ── Column type mapping ──────────────────────────────────────────────────\n\n/**\n * How `information_schema.columns.data_type` spells a planned column type.\n *\n * The catalogue's names are its own — `character varying`, not `varchar`;\n * `ARRAY` for every array whatever its element; `USER-DEFINED` for an enum, a\n * `vector`, and anything else identified by name rather than by shape. So the\n * translation has to exist somewhere. What must *not* exist twice is the step\n * before it: which column a property compiles to.\n *\n * This function was that second step — a fourth copy of the property switch,\n * kept \"in sync\" with the generators by a comment, and already out of step\n * (`geopoint` answered `null` here, so the one type that was genuinely\n * unpersistable was the one the doctor never checked). It reads the schema plan\n * now.\n */\nconst INFORMATION_SCHEMA_NAMES: Record<PgType[\"kind\"], string> = {\n text: \"text\",\n varchar: \"character varying\",\n char: \"character\",\n uuid: \"uuid\",\n // A `CREATE TYPE … AS ENUM` is a name, not a shape.\n enum: \"USER-DEFINED\",\n smallint: \"smallint\",\n integer: \"integer\",\n bigint: \"bigint\",\n // A serial is an integer with a sequence default; the catalogue reports the\n // underlying width and nothing about the sequence.\n smallserial: \"smallint\",\n serial: \"integer\",\n bigserial: \"bigint\",\n real: \"real\",\n doublePrecision: \"double precision\",\n numeric: \"numeric\",\n boolean: \"boolean\",\n timestamptz: \"timestamp with time zone\",\n date: \"date\",\n time: \"time without time zone\",\n json: \"json\",\n jsonb: \"jsonb\",\n // pgvector's type, like every extension type.\n vector: \"USER-DEFINED\",\n bytea: \"bytea\",\n tsvector: \"USER-DEFINED\",\n array: \"ARRAY\"\n};\n\n/**\n * The `data_type` the database should report for a property's column, or `null`\n * when there is nothing to compare.\n *\n * `null` covers the two honest cases: a `relation`, whose column is derived from\n * the relation rather than from the property, and a configuration the planner\n * refuses outright — which is a problem the doctor's other phases report with a\n * message, not a type mismatch to invent one for.\n */\nexport function getExpectedColumnType(prop: Property): string | null {\n // The FK column a relation owns is planned from the relation, and its type\n // is the *target's* key — which a bare property cannot resolve.\n if (prop.type === \"relation\") return null;\n\n // A one-property collection, because the planner asks the questions a\n // column needs in context: is this the primary key, what is the enum type\n // called, what does the link point at. A `reference` whose target is not\n // here resolves to `text`, exactly as it does in a real bundle.\n const collection = {\n slug: \"__doctor__\",\n table: \"__doctor__\",\n properties: { __column__: prop }\n } as unknown as CollectionConfig;\n\n try {\n const type = columnPgType(\"__column__\", prop, collection, () => undefined);\n return INFORMATION_SCHEMA_NAMES[type.kind];\n } catch {\n return null;\n }\n}\n\n// ── Collection loading ───────────────────────────────────────────────────\n\n/**\n * Re-exported so callers keep importing it from here, but the implementation is\n * now shared with the runtime and the generators — a doctor that disagreed with\n * the policy generator about which files are collections would compare the\n * wrong thing.\n */\nexport async function loadCollections(collectionsPath: string): Promise<CollectionConfig[]> {\n return loadCollectionsFromDirectory(collectionsPath);\n}\n\n// ── Phase 1: Collections ↔ Generated Schema ─────────────────────────────\n\nexport async function checkCollectionsVsSchema(\n collections: CollectionConfig[],\n schemaFilePath: string\n): Promise<DoctorPhase> {\n const issues: DoctorIssue[] = [];\n\n // Check if schema file exists\n if (!fs.existsSync(schemaFilePath)) {\n issues.push({\n severity: \"error\",\n category: \"schema_stale\",\n message: \"Generated schema file does not exist.\",\n fix: \"Run `rebase schema generate`\"\n });\n return { passed: false,\nissues };\n }\n\n // Re-generate schema in-memory and compare with file on disk. Only the\n // collections the generator itself will emit — a Firestore collection has\n // no table in the generated file and must not be reported as missing one.\n const postgresCollections = relationalCollections(collections);\n if (postgresCollections.length === 0) {\n return { passed: true,\nissues };\n }\n\n try {\n // Declaration by declaration, not byte by byte. The generated file is a\n // flat list of `export const`s, each the rendering of one piece of the\n // schema plan, so comparing them says *which* table or enum moved —\n // which is what the reader is about to go looking for — and ignores the\n // formatting and commentary the renderer is free to vary on its own.\n const differences = compareGeneratedDeclarations(\n generateSchema(postgresCollections),\n await fsPromises.readFile(schemaFilePath, \"utf-8\")\n );\n\n if (differences.length > 0) {\n issues.push({\n severity: \"warning\",\n category: \"schema_stale\",\n message: \"Generated schema is out of date — collection definitions have changed since last generation. \"\n + `Differs in: ${describeDeclarationDifferences(differences)}.`,\n fix: \"Run `rebase schema generate`\"\n });\n }\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n issues.push({\n severity: \"warning\",\n category: \"schema_stale\",\n message: `Could not regenerate schema for comparison: ${message}`,\n fix: \"Run `rebase schema generate` to verify\"\n });\n }\n\n return { passed: issues.length === 0,\nissues };\n}\n\nexport async function checkCollectionsVsSdk(\n collections: CollectionConfig[],\n sdkFilePath: string\n): Promise<DoctorPhase> {\n const issues: DoctorIssue[] = [];\n\n // The typed SDK is opt-in — nothing in a scaffolded project imports it until\n // you choose to. A project that never generated one isn't drifting, so report\n // it as information rather than a warning; otherwise `doctor` can never come\n // back clean on a fresh project and users learn to ignore its output.\n if (!fs.existsSync(sdkFilePath)) {\n issues.push({\n severity: \"info\",\n category: \"sdk_not_generated\",\n message: \"Typed SDK not generated (optional).\",\n fix: \"Run `rebase generate-sdk` if you want typed collection access\"\n });\n return { passed: true,\nissues,\nnotApplicable: \"not generated (optional)\" };\n }\n\n try {\n const expectedSdk = generateTypedefs(collections);\n const actualSdk = await fsPromises.readFile(sdkFilePath, \"utf-8\");\n\n // Normalize whitespace for comparison\n const normalize = (s: string) =>\n s\n .replace(/\\/\\/.*$/gm, \"\") // strip single-line comments\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, \"\") // strip multi-line comments\n .replace(/\\s+/g, \" \")\n .trim();\n\n if (normalize(expectedSdk) !== normalize(actualSdk)) {\n issues.push({\n severity: \"warning\",\n category: \"sdk_stale\",\n message: \"Generated SDK types are out of date — collection definitions have changed since last SDK generation.\",\n fix: \"Run `rebase generate-sdk`\"\n });\n }\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n\n // A `CodegenError` is not \"the comparison did not run\" — it is the\n // schema saying it cannot produce a valid typed client at all, most\n // often two slugs collapsing onto one accessor. Reporting that as a\n // warning about staleness described the wrong problem and pointed at a\n // command that would fail the same way.\n if (err instanceof Error && err.name === \"CodegenError\") {\n issues.push({\n severity: \"error\",\n category: \"sdk_ungeneratable\",\n message: `The typed SDK cannot be generated from these collections: ${message}`,\n fix: \"Fix the collection definitions named above, then run `rebase generate-sdk`\"\n });\n } else {\n issues.push({\n severity: \"warning\",\n category: \"sdk_stale\",\n message: `Could not regenerate SDK types for comparison: ${message}`,\n fix: \"Run `rebase generate-sdk` to verify\"\n });\n }\n }\n\n return { passed: issues.length === 0,\nissues };\n}\n\n// ── Phase 2: Collections ↔ Database ──────────────────────────────────────\n\ninterface DbColumn {\n table_schema: string;\n table_name: string;\n column_name: string;\n data_type: string;\n is_nullable: string;\n udt_name: string;\n}\n\n\ninterface DbEnumValue {\n enum_schema: string;\n enum_name: string;\n enum_value: string;\n}\n\n/**\n * Extensions the collections need, that the database does not have installed.\n *\n * Only `vector` today, and only because a `{ type: \"vector\" }` property plans a\n * column of a type that will not exist. Rebase installs it only where a project\n * has said it may (`database({ extensions: [\"vector\"] })`), so the absence is\n * often correct configuration and a wrong deployment — which is exactly why the\n * report has to name it rather than the first failing INSERT.\n *\n * Exported for its test.\n */\nexport function missingExtensionIssues(\n collections: CollectionConfig[],\n installed: ReadonlySet<string>\n): DoctorIssue[] {\n const usesVector = collections.some(collection =>\n Object.values(collection.properties ?? {})\n .some(property => (property as { type?: string } | null)?.type === \"vector\"));\n\n if (!usesVector || installed.has(\"vector\")) return [];\n\n return [{\n severity: \"error\",\n category: \"missing_table\",\n message: 'A collection declares a `{ type: \"vector\" }` property and the pgvector extension is not installed on this database.',\n fix: 'Declare it — `database({ extensions: [\"vector\"] })` in config/resources.ts — and use an image that ships the library '\n + \"(the scaffold's pgvector/pgvector:pg18; a stock postgres:18 does not). Or install it once by hand: CREATE EXTENSION vector;\"\n }];\n}\n\n/**\n * The stamp the runtime wrote, against the collections on disk.\n *\n * A stamp that disagrees means this database was provisioned from a different\n * set of collections than the ones in this checkout — a colleague's branch, an\n * older deploy, a tenant somebody else migrated. Everything else in this report\n * is then describing a schema somebody else created, which is worth knowing\n * before acting on any of it.\n *\n * A warning, not an error, and never a direction: the stamp is a hash, so it\n * can say the two disagree and never which is ahead. `null` — never stamped —\n * is not a finding: every database provisioned before the stamp existed reads\n * that way, and so does every fresh one.\n */\nexport async function schemaStampIssues(\n pool: { query<T>(text: string): Promise<{ rows: T[] }> },\n collections: CollectionConfig[]\n): Promise<DoctorIssue[]> {\n let stamped: string | null = null;\n try {\n // `to_regclass` so a missing schema or table is a null rather than a\n // thrown 42P01 — the commonest state on a fresh database is neither.\n const present = await pool.query<{ present: boolean }>(\n `SELECT to_regclass('\"rebase\".\"schema_meta\"') IS NOT NULL AS present`\n );\n if (!present.rows[0]?.present) return [];\n const row = await pool.query<{ value: string }>(\n `SELECT value FROM \"rebase\".\"schema_meta\" WHERE key = 'collections_schema_version' LIMIT 1`\n );\n stamped = row.rows[0]?.value ?? null;\n } catch {\n // The stamp is a diagnostic, not a gate. A database that will not\n // answer this question has bigger problems, and they are reported by\n // the checks around this one.\n return [];\n }\n\n if (!stamped) return [];\n const expected = computeSchemaVersion(collections);\n if (stamped === expected) return [];\n\n return [{\n severity: \"warning\",\n category: \"schema_stale\",\n message: `This database was provisioned from a different set of collections (stamp ${stamped.slice(0, 12)}…, this checkout computes ${expected.slice(0, 12)}…).`,\n fix: \"A hash says the two disagree, never which is ahead. Check you are pointed at the database you think you are; then `rebase db push` to bring it to these collections.\"\n }];\n}\n\n/** `\"public\".\"posts\"` — quoted, for a statement the reader will paste. */\nconst quotedTable = (schema: string, table: string): string => `\"${schema}\".\"${table}\"`;\n\n/**\n * What to do about a column whose type is not the one the collection plans.\n *\n * Both ways out, because the doctor cannot know which side is right: the\n * property moved, or the database did. Putting the property back always clears\n * the finding; converting the column clears it when every value converts.\n */\nexport function typeMismatchFix(\n schema: string,\n table: string,\n column: string,\n actual: string,\n planned: PgType | undefined\n): string {\n const back = `Either put the property back to the column's type (${actual}), or`;\n if (!planned) return `${back} run a migration that converts the column.`;\n const type = renderPgType(planned);\n return `${back} convert the column when you can watch it (every value must convert): `\n + `ALTER TABLE ${quotedTable(schema, table)} ALTER COLUMN \"${column}\" TYPE ${type} USING \"${column}\"::${type};`;\n}\n\n/**\n * The table's primary key against the one the collection reads it by.\n *\n * Rebase addresses a row by the key the plan names — the `isId` property, or\n * the implicit `id` — so a table keyed on other columns, or on none, serves\n * reads and writes against a key that does not hold. An error, because that is\n * what it is at runtime.\n */\nexport function primaryKeyIssues(tablePlan: TablePlan, dbPk: string[], displayName: string): DoctorIssue[] {\n const expected = tablePlan.primaryKey;\n const same = dbPk.length === expected.length && [...dbPk].sort().join(\"\\u0000\") === [...expected].sort().join(\"\\u0000\");\n if (same) return [];\n const table = quotedTable(tablePlan.schema, tablePlan.table);\n const keyList = expected.map(c => `\"${c}\"`).join(\", \");\n if (dbPk.length === 0) {\n return [{\n severity: \"error\",\n category: \"primary_key_mismatch\",\n table: displayName,\n expected: expected.join(\", \"),\n actual: \"(none)\",\n message: `Table \"${displayName}\" has no primary key; the collection reads its rows by ${keyList}.`,\n fix: `ALTER TABLE ${table} ADD PRIMARY KEY (${keyList});`\n }];\n }\n // Several `isId` properties are one composite key, so a table keyed on\n // several columns is matched by marking each of them — and only them.\n const only = expected.some(column => !dbPk.includes(column)) ? \"only \" : \"\";\n const fix = dbPk.length === 1\n ? `Mark ${only}the property for column \"${dbPk[0]}\" with \\`isId\\` — the table is keyed on it — or re-key the table on ${keyList}.`\n : `Mark ${only}the properties for columns ${dbPk.map(c => `\"${c}\"`).join(\", \")} with \\`isId\\` — together they are `\n + `the table's composite key — or re-key the table on ${keyList}.`;\n return [{\n severity: \"error\",\n category: \"primary_key_mismatch\",\n table: displayName,\n expected: expected.join(\", \"),\n actual: dbPk.join(\", \"),\n message: `Table \"${displayName}\" is keyed on (${dbPk.join(\", \")}), but the collection reads its rows by ${keyList}.`,\n fix\n }];\n}\n\n/**\n * A junction's two key columns: present, typed like the keys they point at,\n * and the table keyed on the pair. Order is not compared — boot and `db push`\n * have built the key in both orders, and either one serves the same rows.\n */\nexport function junctionKeyIssues(\n junctionPlan: TablePlan,\n dbColumns: DbColumn[],\n dbPk: string[],\n displayName: string\n): DoctorIssue[] {\n const issues: DoctorIssue[] = [];\n const byName = new Map(dbColumns.map(c => [c.column_name, c]));\n for (const column of junctionPlan.columns) {\n const dbCol = byName.get(column.column);\n if (!dbCol) {\n issues.push({\n severity: \"error\",\n category: \"missing_column\",\n table: displayName,\n column: column.column,\n message: `Junction table \"${displayName}\" has no \"${column.column}\" column.`,\n fix: \"Run `rebase db push` or `rebase db generate && rebase db migrate`\"\n });\n continue;\n }\n const expected = INFORMATION_SCHEMA_NAMES[column.type.kind];\n if (dbCol.data_type !== expected) {\n issues.push({\n severity: \"warning\",\n category: \"type_mismatch\",\n table: displayName,\n column: column.column,\n expected,\n actual: dbCol.data_type,\n message: `Junction column \"${column.column}\" in \"${displayName}\" is ${dbCol.data_type}, but the key it points at is ${expected}.`,\n fix: typeMismatchFix(junctionPlan.schema, junctionPlan.table, column.column, dbCol.data_type, column.type)\n });\n }\n }\n issues.push(...primaryKeyIssues(junctionPlan, dbPk, displayName));\n return issues;\n}\n\nexport async function checkCollectionsVsDatabase(\n collections: CollectionConfig[],\n databaseUrl: string\n): Promise<DoctorPhase> {\n const issues: DoctorIssue[] = [];\n\n // Dynamic import to avoid loading pg when not needed\n const pgModule = await import(\"pg\");\n const { Pool } = pgModule.default ?? pgModule;\n const pool = new Pool({ connectionString: databaseUrl });\n\n // Determine all schemas defined by the collections, plus public and rebase\n const schemas = Array.from(new Set([\n \"public\",\n \"rebase\",\n ...relationalCollections(collections)\n .filter(isPostgresCollectionConfig)\n .map(c => c.schema)\n .filter((s): s is string => !!s)\n ]));\n\n try {\n // Fetch all tables in the defined schemas\n const tablesResult = await pool.query<{ table_schema: string; table_name: string }>(\n `SELECT table_schema, table_name \n FROM information_schema.tables \n WHERE table_schema = ANY($1) AND table_type = 'BASE TABLE'`,\n [schemas]\n );\n const existingTables = new Set(tablesResult.rows.map((r) =>\n r.table_schema === \"public\" ? r.table_name : `${r.table_schema}.${r.table_name}`\n ));\n\n // An extension the collections need and the database does not have.\n //\n // The symptom without this is `type \"vector\" does not exist` on the\n // first write to a table that looks perfectly well-formed in the\n // report: the column is planned, the extension is not, and the two\n // facts live in different places. Extensions are opt-in by design —\n // installing one needs an image that ships the library and a role\n // allowed to install it — so this reports, it does not install.\n const extensionsResult = await pool.query<{ extname: string }>(\"SELECT extname FROM pg_extension\");\n const installedExtensions = new Set(extensionsResult.rows.map(r => r.extname));\n issues.push(...missingExtensionIssues(collections, installedExtensions));\n\n // A database provisioned from a different set of collections than the\n // ones on disk.\n //\n // The runtime stamps what it applied; nothing compared it outside boot,\n // and a stamp that disagrees is exactly the state where the rest of\n // this report is describing a schema somebody else's deploy created.\n issues.push(...await schemaStampIssues(pool, collections));\n\n // Fetch all columns in the defined schemas\n const columnsResult = await pool.query<DbColumn>(\n `SELECT table_schema, table_name, column_name, data_type, is_nullable, udt_name\n FROM information_schema.columns\n WHERE table_schema = ANY($1)\n ORDER BY table_schema, table_name, ordinal_position`,\n [schemas]\n );\n const columnsByTable = new Map<string, DbColumn[]>();\n for (const row of columnsResult.rows) {\n const tableSchema = row.table_schema;\n const tableName = row.table_name;\n const key = tableSchema === \"public\" ? tableName : `${tableSchema}.${tableName}`;\n if (!columnsByTable.has(key)) {\n columnsByTable.set(key, []);\n }\n columnsByTable.get(key)!.push(row);\n }\n\n // Fetch enums, keyed `schema.name`. A type is found in the schema the\n // planner put it in, and nowhere else — a same-named type in another\n // schema is somebody else's, and the column does not use it.\n const enumsResult = await pool.query<DbEnumValue>(\n `SELECT n.nspname AS enum_schema, t.typname AS enum_name, e.enumlabel AS enum_value\n FROM pg_type t\n JOIN pg_namespace n ON n.oid = t.typnamespace\n JOIN pg_enum e ON t.oid = e.enumtypid\n ORDER BY n.nspname, t.typname, e.enumsortorder`\n );\n const enumsByName = new Map<string, string[]>();\n for (const row of enumsResult.rows) {\n const key = `${row.enum_schema}.${row.enum_name}`;\n if (!enumsByName.has(key)) {\n enumsByName.set(key, []);\n }\n enumsByName.get(key)!.push(row.enum_value);\n }\n\n // Every table's primary key, its columns in key order.\n const pksResult = await pool.query<{ table_schema: string; table_name: string; columns: string[] }>(\n `SELECT n.nspname AS table_schema, c.relname AS table_name,\n array_agg(a.attname::text ORDER BY array_position(i.indkey::int2[], a.attnum)) AS columns\n FROM pg_index i\n JOIN pg_class c ON c.oid = i.indrelid\n JOIN pg_namespace n ON n.oid = c.relnamespace\n JOIN pg_attribute a ON a.attrelid = c.oid AND a.attnum = ANY(i.indkey::int2[])\n WHERE i.indisprimary AND n.nspname = ANY($1)\n GROUP BY n.nspname, c.relname`,\n [schemas]\n );\n const pksByTable = new Map<string, string[]>();\n for (const row of pksResult.rows) {\n pksByTable.set(row.table_schema === \"public\" ? row.table_name : `${row.table_schema}.${row.table_name}`, row.columns);\n }\n\n // Fetch foreign key constraints in the defined schemas\n const fksResult = await pool.query<{\n constraint_name: string;\n table_schema: string;\n table_name: string;\n column_name: string;\n foreign_table_schema: string;\n foreign_table_name: string;\n foreign_column_name: string;\n }>(\n `SELECT\n tc.constraint_name,\n tc.table_schema,\n tc.table_name,\n kcu.column_name,\n ccu.table_schema AS foreign_table_schema,\n ccu.table_name AS foreign_table_name,\n ccu.column_name AS foreign_column_name\n FROM information_schema.table_constraints AS tc\n JOIN information_schema.key_column_usage AS kcu\n ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema\n JOIN information_schema.constraint_column_usage AS ccu\n ON ccu.constraint_name = tc.constraint_name AND ccu.table_schema = tc.table_schema\n WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_schema = ANY($1)`,\n [schemas]\n );\n const fksByTable = new Map<string, typeof fksResult.rows>();\n for (const row of fksResult.rows) {\n const tableSchema = row.table_schema;\n const tableName = row.table_name;\n const key = tableSchema === \"public\" ? tableName : `${tableSchema}.${tableName}`;\n if (!fksByTable.has(key)) {\n fksByTable.set(key, []);\n }\n fksByTable.get(key)!.push(row);\n }\n\n // ── Compare each collection against the database ─────────────────\n\n const postgresCollections = relationalCollections(collections);\n const junctionSpecs = resolveJunctionSpecs(postgresCollections);\n\n // What push and boot build, read once. The key and the junction checks\n // compare against it rather than against a fifth reading of the\n // properties. A configuration the planner refuses has no plan; the\n // other phases name that problem, and these checks stand down.\n let plan: SchemaPlan | undefined;\n try {\n plan = planSchema(postgresCollections);\n } catch {\n plan = undefined;\n }\n const tablePlans = new Map<string, TablePlan>((plan?.tables ?? []).map(t => [t.qualified, t]));\n const checkedJunctions = new Set<string>();\n\n for (const collection of postgresCollections) {\n const tableName = getTableName(collection);\n const schemaName = (collection as { schema?: string }).schema || \"public\";\n const fullTableName = schemaName === \"public\" ? tableName : `${schemaName}.${tableName}`;\n\n // Check table existence\n if (!existingTables.has(fullTableName)) {\n issues.push({\n severity: \"error\",\n category: \"missing_table\",\n table: fullTableName,\n message: `Table \"${fullTableName}\" does not exist in the database.`,\n fix: \"Run `rebase db push` or `rebase db generate && rebase db migrate`\"\n });\n continue; // Skip column checks for missing tables\n }\n\n const dbColumns = columnsByTable.get(fullTableName) ?? [];\n const dbColumnMap = new Map(dbColumns.map((c) => [c.column_name, c]));\n\n // The key. It used to be skipped outright — `id` sat in a list of\n // \"system columns\" — so a key whose type or whose columns had\n // drifted reported \"In sync\".\n const tablePlan = tablePlans.get(`${schemaName}.${tableName}`);\n if (tablePlan) {\n issues.push(...primaryKeyIssues(tablePlan, pksByTable.get(fullTableName) ?? [], fullTableName));\n }\n\n // Check properties → columns\n for (const [propName, prop] of Object.entries(collection.properties ?? {})) {\n if (prop.type === \"relation\") {\n // ResolvedRelation columns are derived from localKey\n const resolvedRelations = resolveCollectionRelations(collection);\n const relation = findRelation(resolvedRelations, (prop as RelationProperty).relation?.relationName ?? propName);\n if (relation?.kind === \"belongsTo\") {\n const fkColName = relation.localKey;\n if (!dbColumnMap.has(fkColName)) {\n issues.push({\n severity: \"error\",\n category: \"missing_column\",\n table: fullTableName,\n column: fkColName,\n message: `Foreign key column \"${fkColName}\" for relation \"${propName}\" is missing from table \"${fullTableName}\".`,\n fix: \"Run `rebase db push` or `rebase db generate && rebase db migrate`\"\n });\n }\n\n // Check FK constraint exists\n const tableFks = fksByTable.get(fullTableName) ?? [];\n let targetTableName = \"unknown\";\n let targetSchemaName = \"public\";\n try {\n const targetColl = relation.target();\n targetTableName = getTableName(targetColl);\n targetSchemaName = (targetColl as { schema?: string }).schema || \"public\";\n } catch { /* ignore */ }\n\n const hasFk = tableFks.some((fk) =>\n fk.column_name === fkColName &&\n fk.foreign_table_name === targetTableName &&\n fk.foreign_table_schema === targetSchemaName\n );\n\n if (dbColumnMap.has(fkColName) && !hasFk) {\n issues.push({\n severity: \"warning\",\n category: \"missing_foreign_key\",\n table: fullTableName,\n column: fkColName,\n message: `Column \"${fkColName}\" exists but has no FOREIGN KEY constraint referencing \"${targetSchemaName === \"public\" ? targetTableName : `${targetSchemaName}.${targetTableName}`}\".`,\n fix: \"Run `rebase db push` or add the constraint manually\"\n });\n }\n }\n continue;\n }\n\n const colName = resolveColumnName(propName, prop);\n\n const dbCol = dbColumnMap.get(colName);\n if (!dbCol) {\n issues.push({\n severity: \"error\",\n category: \"missing_column\",\n table: fullTableName,\n column: colName,\n message: `Column \"${colName}\" is defined in collection \"${collection.slug}\" but missing from table \"${fullTableName}\".`,\n fix: \"Run `rebase db push` or `rebase db generate && rebase db migrate`\"\n });\n continue;\n }\n\n // Type check\n const expectedType = getExpectedColumnType(prop);\n if (expectedType) {\n const actualType = dbCol.data_type;\n let isMismatch = actualType !== expectedType;\n if (prop.type === \"vector\" && dbCol.udt_name !== \"vector\") {\n isMismatch = true;\n }\n if (prop.type === \"array\") {\n const ap = prop as ArrayProperty;\n let expectedColType = ap.columnType;\n if (!expectedColType && ap.of && !Array.isArray(ap.of)) {\n const ofProp = ap.of as Property;\n if (ofProp.type === \"string\") expectedColType = \"text[]\";\n else if (ofProp.type === \"number\") expectedColType = ofProp.validation?.integer ? \"integer[]\" : \"numeric[]\";\n else if (ofProp.type === \"boolean\") expectedColType = \"boolean[]\";\n }\n if (expectedColType && expectedColType.endsWith(\"[]\")) {\n if (actualType !== \"ARRAY\") {\n isMismatch = true;\n } else {\n const expectedUdt = expectedColType === \"text[]\" ? \"_text\" :\n expectedColType === \"integer[]\" ? \"_int4\" :\n expectedColType === \"boolean[]\" ? \"_bool\" :\n expectedColType === \"numeric[]\" ? \"_numeric\" : \"\";\n if (expectedUdt && dbCol.udt_name !== expectedUdt) {\n isMismatch = true;\n }\n }\n }\n }\n if (isMismatch) {\n const planned = tablePlan?.columns.find(c => c.column === colName)?.type;\n issues.push({\n severity: \"warning\",\n category: \"type_mismatch\",\n table: fullTableName,\n column: colName,\n expected: prop.type === \"vector\" ? \"vector\" : expectedType,\n actual: dbCol.udt_name === \"vector\" ? \"vector\" : actualType,\n message: `Column \"${colName}\" in table \"${fullTableName}\": expected type \"${prop.type === \"vector\" ? \"vector\" : expectedType}\" but found \"${dbCol.udt_name === \"vector\" ? \"vector\" : actualType}\".`,\n fix: typeMismatchFix(schemaName, tableName, colName, actualType, planned)\n });\n }\n }\n\n // Enum value check\n if (prop.type === \"string\" && (prop as StringProperty).enum) {\n const enumValues = (prop as StringProperty).enum;\n if (enumValues) {\n // The planner's name: `<table>_<column>`, cut to the 63\n // bytes Postgres keeps, in the collection's schema.\n const enumName = toPostgresIdentifier(`${tableName}_${colName}`);\n const dbEnumValues = enumsByName.get(`${schemaName}.${enumName}`);\n if (!dbEnumValues) {\n issues.push({\n severity: \"warning\",\n category: \"missing_enum\",\n table: fullTableName,\n column: colName,\n expected: enumName,\n message: `Enum type \"${schemaName}\".\"${enumName}\" is defined in collection but not found in the database.`,\n fix: \"Run `rebase db push` or `rebase db generate && rebase db migrate`\"\n });\n } else {\n // Compare enum values\n const expectedValues = Array.isArray(enumValues)\n ? enumValues.map((v) => (typeof v === \"string\" ? v : String(v.id)))\n : Object.keys(enumValues);\n\n const missing = expectedValues.filter((v) => !dbEnumValues.includes(v));\n const extra = dbEnumValues.filter((v) => !expectedValues.includes(v));\n\n if (missing.length > 0 || extra.length > 0) {\n const parts: string[] = [];\n if (missing.length > 0) parts.push(`missing: ${missing.join(\", \")}`);\n if (extra.length > 0) parts.push(`extra in DB: ${extra.join(\", \")}`);\n issues.push({\n severity: \"warning\",\n category: \"enum_value_mismatch\",\n table: fullTableName,\n column: colName,\n expected: expectedValues.join(\", \"),\n actual: dbEnumValues.join(\", \"),\n message: `Enum values for \"${colName}\" in table \"${fullTableName}\" are out of sync (${parts.join(\"; \")}).`,\n fix: extra.length > 0 && missing.length === 0\n // A push cannot remove a label (no ALTER TYPE … DROP VALUE), so it was never the remedy for these.\n ? `PostgreSQL cannot drop an enum label, so \\`rebase db push\\` leaves ${extra.join(\", \")} in place. Harmless unless something still writes it; to retire it, UPDATE its rows to another value, then rewrite the type in a migration (\\`rebase db generate\\`: CREATE TYPE …, ALTER TABLE … TYPE … USING, DROP TYPE) and \\`rebase db migrate\\``\n : \"Run `rebase db push` to add the missing labels\" + (extra.length > 0 ? `; it cannot remove ${extra.join(\", \")} (PostgreSQL has no ALTER TYPE … DROP VALUE) — retire those with a migration that rewrites the type` : \"\")\n });\n }\n }\n }\n }\n }\n\n // Also check junction tables for many-to-many relations — where\n // `resolveJunctionSpecs` says they are, which is where boot and\n // `db push` create them: `public`, whatever schema the declaring\n // collection is in, under the name with any schema prefix removed.\n const resolvedRelations = resolveCollectionRelations(collection);\n for (const relation of Object.values(resolvedRelations)) {\n if (isManyToMany(relation)) {\n const junctionTable = relation.through.table.split(\".\").pop() ?? relation.through.table;\n const junctionSchema = junctionSpecs.get(junctionTable)?.schema ?? \"public\";\n const fullJunctionTable = junctionSchema === \"public\" ? junctionTable : `${junctionSchema}.${junctionTable}`;\n if (!existingTables.has(fullJunctionTable)) {\n issues.push({\n severity: \"error\",\n category: \"missing_table\",\n table: fullJunctionTable,\n message: `Junction table \"${fullJunctionTable}\" for many-to-many relation \"${relation.relationName}\" is missing.`,\n fix: \"Run `rebase db push` or `rebase db generate && rebase db migrate`\"\n });\n } else if (!checkedJunctions.has(fullJunctionTable)) {\n // Both sides declare the same junction; it is checked once.\n checkedJunctions.add(fullJunctionTable);\n const junctionPlan = tablePlans.get(`${junctionSchema}.${junctionTable}`);\n if (junctionPlan) {\n issues.push(...junctionKeyIssues(\n junctionPlan,\n columnsByTable.get(fullJunctionTable) ?? [],\n pksByTable.get(fullJunctionTable) ?? [],\n fullJunctionTable\n ));\n }\n }\n }\n }\n }\n\n if (plan) {\n issues.push(...orphanedJunctionIssues(plan, columnsByTable, pksByTable, fksByTable));\n }\n } finally {\n await pool.end();\n }\n\n // An `info` note — the orphaned junction — does not make the phase fail.\n return { passed: !issues.some(issue => issue.severity !== \"info\"),\nissues };\n}\n\n/**\n * A `public` table in the junction shape that no collection declares.\n *\n * The shape is the planner's: exactly two columns, together the primary key,\n * each a single-column foreign key to a collection's table. Renaming or\n * removing a many-to-many leaves such a table behind — push and boot only\n * create — with every link row still in it and nothing reading it.\n *\n * Info, never more: the table may be data somebody still wants. The remedy is\n * shown, never run.\n */\nexport function orphanedJunctionIssues(\n plan: SchemaPlan,\n columnsByTable: Map<string, { column_name: string }[]>,\n pksByTable: Map<string, string[]>,\n fksByTable: Map<string, { constraint_name: string; column_name: string; foreign_table_schema: string; foreign_table_name: string }[]>\n): DoctorIssue[] {\n const declared = new Set(plan.tables.map(table => table.qualified));\n const collectionTables = new Set(plan.tables.filter(table => table.kind === \"collection\").map(table => table.qualified));\n const issues: DoctorIssue[] = [];\n for (const [table, columns] of columnsByTable) {\n // Keys are bare for `public` and `schema.table` elsewhere.\n if (table.includes(\".\") || declared.has(`public.${table}`)) continue;\n // Two columns and a two-column key: the key is those two columns.\n if (columns.length !== 2 || (pksByTable.get(table) ?? []).length !== 2) continue;\n\n const byConstraint = new Map<string, { column: string; target: string }[]>();\n for (const fk of fksByTable.get(table) ?? []) {\n const target = `${fk.foreign_table_schema}.${fk.foreign_table_name}`;\n byConstraint.set(fk.constraint_name, [...(byConstraint.get(fk.constraint_name) ?? []), { column: fk.column_name, target }]);\n }\n const links = [...byConstraint.values()];\n if (links.length !== 2 || links.some(link => link.length !== 1)) continue;\n if (new Set(links.map(([link]) => link.column)).size !== 2) continue;\n if (!links.every(([link]) => collectionTables.has(link.target))) continue;\n\n const targets = links.map(([link]) => link.target.replace(/^public\\./, \"\"));\n issues.push({\n severity: \"info\",\n category: \"orphaned_junction\",\n table,\n message: `Table \"${table}\" links \"${targets[0]}\" and \"${targets[1]}\" like a many-to-many junction, but no collection declares it — left behind by a renamed or removed relation?`,\n fix: `DROP TABLE \"public\".\"${table}\";`\n });\n }\n return issues;\n}\n\n// ── Report Rendering ─────────────────────────────────────────────────────\n\nexport function renderReport(report: DoctorReport): void {\n out();\n out(chalk.bold(\" 🩺 Rebase Schema Doctor\"));\n out(chalk.gray(\" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"));\n out();\n\n // Phase 1\n renderPhase(\"Collections → Generated Schema\", report.collectionsToSchema);\n\n // Phase 2\n renderPhase(\"Collections → Database\", report.schemaToDatabase);\n\n // Phase 3\n renderPhase(\"Collections → SDK Types\", report.collectionsToSdk);\n\n // Summary\n out(chalk.gray(\" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"));\n const { passed, skipped, notApplicable, warnings, errors } = report.summary;\n\n const parts: string[] = [];\n parts.push(chalk.green(`${passed} passed`));\n if (skipped > 0) parts.push(chalk.yellow(`${skipped} skipped`));\n if (notApplicable > 0) parts.push(chalk.gray(`${notApplicable} not applicable`));\n if (warnings > 0) parts.push(chalk.yellow(`${warnings} warnings`));\n if (errors > 0) parts.push(chalk.red(`${errors} errors`));\n\n out(` Summary: ${parts.join(\", \")}`);\n out();\n\n if (errors > 0) {\n out(chalk.red.bold(\" ✗ Schema drift detected. Run the suggested fixes above.\"));\n } else if (warnings > 0) {\n out(chalk.yellow.bold(\" ⚠ Minor issues detected. Consider running the suggested fixes.\"));\n } else if (skipped > 0) {\n // Never \"all schemas are in sync\" off the back of a check that did not\n // run: a clean bill of health has to come from measurement.\n out(chalk.yellow.bold(` ⚠ Nothing wrong in the checks that ran, but ${skipped} did not run — this is not a clean bill of health.`));\n } else {\n out(chalk.green.bold(\" ✓ All schemas are in sync!\"));\n }\n out();\n}\n\nfunction renderPhase(label: string, phase: DoctorPhase): void {\n // A phase that never ran gets its own marker and its reason. It is neither\n // \"In sync\" nor a drift report; conflating it with the former is what let\n // doctor certify a database it had not opened.\n if (phase.skipped) {\n out(` ${chalk.yellow(\"⏭\")} ${label}: ${chalk.yellow(`skipped (${phase.skipped})`)}`);\n out();\n return;\n }\n\n // Nothing to compare against. Its own marker, and only the remedy under it\n // — the phase header already carries the reason, so re-printing the note\n // would say the same thing twice. See DoctorPhase.notApplicable.\n if (phase.notApplicable) {\n out(` ${chalk.gray(\"➖\")} ${label}: ${chalk.gray(phase.notApplicable)}`);\n for (const issue of phase.issues.filter((i) => i.severity === \"info\")) {\n out(` ${chalk.gray(issue.fix)}`);\n }\n out();\n return;\n }\n\n const issues = phase.issues;\n const errorCount = issues.filter((i) => i.severity === \"error\").length;\n const warnCount = issues.filter((i) => i.severity === \"warning\").length;\n const infoIssues = issues.filter((i) => i.severity === \"info\");\n\n // Informational notes don't make a phase unhealthy, so key the header off\n // real problems rather than `phase.passed` alone.\n if (errorCount === 0 && warnCount === 0) {\n out(` ${chalk.green(\"✅\")} ${label}: ${chalk.green(\"In sync\")}`);\n } else {\n const parts: string[] = [];\n if (errorCount > 0) parts.push(`${errorCount} error${errorCount > 1 ? \"s\" : \"\"}`);\n if (warnCount > 0) parts.push(`${warnCount} warning${warnCount > 1 ? \"s\" : \"\"}`);\n out(` ${chalk.yellow(\"⚠️\")} ${label}: ${chalk.yellow(parts.join(\", \"))}`);\n }\n out();\n\n // Notes render as a quiet one-liner, not a full drift box.\n for (const issue of infoIssues) {\n const fixPart = issue.fix ? chalk.gray(` — ${issue.fix}`) : \"\";\n out(` ${chalk.gray(\"ℹ\")} ${chalk.gray(issue.message)}${fixPart}`);\n out();\n }\n\n for (const issue of issues.filter((i) => i.severity !== \"info\")) {\n const severityIcon = issue.severity === \"error\" ? chalk.red(\"✗\") : chalk.yellow(\"⚠\");\n const categoryLabel = formatCategory(issue.category);\n out(` ${chalk.gray(\"┌─\")} ${severityIcon} ${chalk.bold(categoryLabel)} ${chalk.gray(\"─\".repeat(Math.max(0, 42 - categoryLabel.length)))}`);\n\n if (issue.table) {\n const colPart = issue.column ? ` │ Column: ${chalk.cyan(issue.column)}` : \"\";\n out(` ${chalk.gray(\"│\")} Table: ${chalk.cyan(issue.table)}${colPart}`);\n }\n\n if (issue.expected && issue.actual) {\n out(` ${chalk.gray(\"│\")} Expected: ${chalk.green(issue.expected)} │ Actual: ${chalk.red(issue.actual)}`);\n }\n\n out(` ${chalk.gray(\"│\")} ${issue.message}`);\n out(` ${chalk.gray(\"│\")} Fix: ${chalk.blue(issue.fix)}`);\n out(` ${chalk.gray(\"└\" + \"─\".repeat(48))}`);\n out();\n }\n}\n\nfunction formatCategory(cat: DoctorIssue[\"category\"]): string {\n const labels: Record<DoctorIssue[\"category\"], string> = {\n missing_table: \"Missing Table\",\n missing_column: \"Missing Column\",\n type_mismatch: \"Type Mismatch\",\n missing_constraint: \"Missing Constraint\",\n primary_key_mismatch: \"Primary Key Mismatch\",\n schema_stale: \"Stale Schema\",\n missing_enum: \"Missing Enum\",\n enum_value_mismatch: \"Enum Value Mismatch\",\n missing_foreign_key: \"Missing Foreign Key\",\n orphaned_junction: \"Orphaned Junction Table\",\n sdk_stale: \"Stale SDK Types\",\n sdk_not_generated: \"SDK Types Not Generated\",\n sdk_ungeneratable: \"SDK Cannot Be Generated\"\n };\n return labels[cat];\n}\n\n// ── Main entry point ─────────────────────────────────────────────────────\n\nexport async function runDoctor(options: {\n collectionsPath: string;\n schemaPath: string;\n sdkPath: string;\n databaseUrl?: string;\n}): Promise<DoctorReport> {\n out();\n out(chalk.bold(\" 🩺 Loading collections...\"));\n const collections = await loadCollections(options.collectionsPath);\n if (collections.length === 0) {\n outError(chalk.red(\" ✗ No collections found.\"));\n process.exit(1);\n }\n out(chalk.gray(` Found ${collections.length} collection(s)`));\n out();\n\n // Phase 1: Collections ↔ Generated Schema\n out(chalk.gray(\" Checking Collections → Generated Schema...\"));\n const collectionsToSchema = await checkCollectionsVsSchema(collections, options.schemaPath);\n\n // Phase 2: Collections ↔ Database (only if we have a DATABASE_URL)\n let schemaToDatabase: DoctorPhase;\n if (options.databaseUrl) {\n out(chalk.gray(\" Checking Collections → Database...\"));\n try {\n schemaToDatabase = await checkCollectionsVsDatabase(collections, options.databaseUrl);\n } catch (err: unknown) {\n // A database that cannot be reached used to end the whole run: the\n // error escaped `runDoctor` and the CLI's last-resort catch printed\n // a stack. So the two phases that need no database — the generated\n // schema and the SDK types, both of which are read off disk — never\n // ran, and the one command whose job is to say what is wrong said\n // only \"Doctor failed\" over a drizzle stack trace.\n const banner = diagnoseDbError(err, options.databaseUrl);\n if (banner) outError(banner);\n const reason = deepestErrorMessage(err)\n ?? (err instanceof Error ? err.message : String(err));\n schemaToDatabase = {\n passed: false,\n issues: [],\n skipped: `could not reach ${parseHostInfo(options.databaseUrl)} — ${reason}`,\n blocked: true\n };\n }\n } else {\n // Not `{ passed: true }`: see DoctorPhase.skipped.\n schemaToDatabase = { passed: false,\nissues: [],\nskipped: \"DATABASE_URL not set\" };\n out(chalk.yellow(\" ⚠ DATABASE_URL not set — skipping database comparison.\"));\n out(chalk.gray(\" Set DATABASE_URL in your .env to enable full drift detection.\"));\n }\n\n // Phase 3: Collections ↔ SDK Types\n out(chalk.gray(\" Checking Collections → SDK Types...\"));\n const collectionsToSdk = await checkCollectionsVsSdk(collections, options.sdkPath);\n\n const phases = [collectionsToSchema, schemaToDatabase, collectionsToSdk];\n const allIssues = phases.flatMap((p) => p.issues);\n const summary = {\n // A skipped phase is not a passing one, however few issues it collected.\n // Nor is one with nothing to compare against: \"passed\" would put a\n // never-generated SDK in the same column as a verified one.\n passed: phases.filter((p) => p.passed && !p.skipped && !p.notApplicable).length,\n skipped: phases.filter((p) => p.skipped).length,\n notApplicable: phases.filter((p) => p.notApplicable && !p.skipped).length,\n warnings: allIssues.filter((i) => i.severity === \"warning\").length,\n errors: allIssues.filter((i) => i.severity === \"error\").length\n };\n\n const report: DoctorReport = { collectionsToSchema,\ncollectionsToSdk,\nschemaToDatabase,\nsummary };\n renderReport(report);\n\n return report;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAoBA,IAAI,eAAe,cAAc,MAAM;CACtC,YAAY,SAAS;EACpB,MAAM,OAAO;EACb,KAAK,OAAO;CACb;AACD;AACA,IAAI,aAAa;;;;;;;;;;;AAWjB,SAAS,QAAQ,KAAK;CACrB,OAAO,WAAW,KAAK,GAAG,IAAI,MAAM,KAAK,UAAU,GAAG;AACvD;;;;;;;;;AASA,SAAS,WAAW,OAAO;CAC1B,OAAO,KAAK,UAAU,KAAK;AAC5B;;AAEA,SAAS,QAAQ,KAAK;CACrB,IAAI,MAAM,QAAQ,GAAG,GAAG,OAAO,IAAI,KAAK,UAAU,SAAS,OAAO,UAAU,WAAW,MAAM,KAAK,KAAK;CACvG,IAAI,OAAO,OAAO,QAAQ,UAAU,OAAO,OAAO,KAAK,GAAG;CAC1D,OAAO,CAAC;AACT;AACA,SAAS,yBAAyB,MAAM;CACvC,QAAQ,KAAK,MAAb;EACC,KAAK,UAAU;GACd,MAAM,KAAK;GACX,IAAI,GAAG,MAAM;IACZ,MAAM,MAAM,QAAQ,GAAG,IAAI;IAC3B,IAAI,IAAI,WAAW,GAAG,OAAO;IAC7B,OAAO,IAAI,KAAK,MAAM,WAAW,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK;GACxD;GACA,OAAO;EACR;EACA,KAAK,UAAU;GACd,MAAM,KAAK;GACX,IAAI,GAAG,MAAM;IACZ,MAAM,MAAM,QAAQ,GAAG,IAAI;IAC3B,MAAM,UAAU,IAAI,IAAI,MAAM;IAC9B,IAAI,IAAI,WAAW,KAAK,QAAQ,MAAM,MAAM,CAAC,OAAO,SAAS,CAAC,CAAC,GAAG,OAAO;IACzE,OAAO,QAAQ,KAAK,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK;GAChD;GACA,OAAO;EACR;EACA,KAAK,WAAW,OAAO;EACvB,KAAK,QAAQ,OAAO;EACpB,KAAK,YAAY,OAAO;EACxB,KAAK,aAAa,OAAO;EACzB,KAAK,YAAY,OAAO;EACxB,KAAK,OAAO;GACX,MAAM,UAAU;GAChB,IAAI,QAAQ,YAAY,OAAO,KAAK,OAAO,QAAQ,QAAQ,UAAU,CAAC,CAAC,KAAK,CAAC,GAAG,OAAO;IACtF,MAAM,QAAQ;IACd,MAAM,WAAW,CAAC,MAAM,YAAY;IACpC,MAAM,OAAO,yBAAyB,KAAK;IAC3C,OAAO,GAAG,QAAQ,CAAC,IAAI,WAAW,MAAM,GAAG,IAAI,WAAW,GAAG,KAAK,WAAW,KAAK;GACnF,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE;GACb,OAAO;EACR;EACA,KAAK,SAAS;GACb,MAAM,UAAU;GAChB,IAAI,QAAQ,IAAI,OAAO,SAAS,yBAAyB,QAAQ,EAAE,EAAE;GACrE,OAAO;EACR;EACA,KAAK,UAAU,OAAO;EACtB,KAAK,UAAU,OAAO;EACtB,SAAS,OAAO;CACjB;AACD;;;;;AAKA,SAAS,wBAAwB,UAAU;CAC1C,IAAI;EACH,IAAI,SAAS,SAAS,OAAO;EAC7B,IAAI,WAAW,OAAO,WAAW,OAAO,aAAa,SAAS,OAAO,WAAW;EAChF,OAAO;CACR,QAAQ;EACP;CACD;AACD;;AAEA,SAAS,eAAe,UAAU;CACjC,MAAM,SAAS,wBAAwB,QAAQ;CAC/C,IAAI,CAAC,QAAQ,YAAY,OAAO;CAChC,MAAM,SAAS,OAAO,QAAQ,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,GAAG,OAAO,EAAE,IAAI;CACxE,IAAI,CAAC,QAAQ,OAAO;CACpB,OAAO,OAAO,EAAE,CAAC,SAAS,WAAW,WAAW;AACjD;;;;;;;;;;;AAWA,SAAS,aAAa,MAAM;CAC3B,OAAO,KAAK,SAAS,YAAY,KAAK,SAAS,WAAW,KAAK,OAAO,KAAK;AAC5E;;AAEA,SAAS,aAAa,MAAM;CAC3B,OAAO,QAAQ,aAAa,IAAI,CAAC;AAClC;;;;;AAKA,SAAS,iBAAiB,MAAM;CAC/B,MAAM,OAAO,aAAa,IAAI;CAC9B,OAAO,QAAQ,IAAI,KAAK,SAAS,YAAY,SAAS;AACvD;;;;;;;;;;;;;AAaA,SAAS,qBAAqB,UAAU,WAAW;CAClD,MAAM,OAAO,wBAAwB,QAAQ,CAAC,EAAE,QAAQ,SAAS;CACjE,MAAM,WAAW,OAAO,UAAU,IAAI,IAAI,IAAI,KAAK;CACnD,MAAM,UAAU,WAAW,YAAY,WAAW,QAAQ,EAAE,YAAY;CACxE,MAAM,QAAQ,SAAS,SAAS,eAAe,UAAU,SAAS,QAAQ,YAAY,MAAM,IAAI,KAAK;CACrG,MAAM,cAAc,QAAQ,GAAG,QAAQ,gBAAgB,MAAM,MAAM;CACnE,OAAO,SAAS,gBAAgB,SAAS,SAAS,YAAY,KAAK;AACpE;;;;;;;;;;;AAWA,SAAS,eAAe,aAAa;CACpC,MAAM,4BAA4B,IAAI,IAAI;CAC1C,MAAM,6BAA6B,IAAI,IAAI;CAC3C,KAAK,MAAM,cAAc,aAAa;EACrC,MAAM,OAAO,WAAW;EACxB,IAAI,OAAO,SAAS,YAAY,KAAK,WAAW,GAAG,MAAM,IAAI,aAAa,6GAA6G;EACvL,MAAM,OAAO,iBAAiB,IAAI;EAClC,IAAI,KAAK,WAAW,GAAG,MAAM,IAAI,aAAa,YAAY,WAAW,IAAI,EAAE,mKAAmK;EAC9O,MAAM,WAAW,qBAAqB,IAAI;EAC1C,IAAI,aAAa,MAAM,QAAQ,KAAK,4BAA4B,WAAW,IAAI,EAAE,0BAA0B,KAAK,yEAAyE,SAAS,+BAA+B,WAAW,IAAI,EAAE,oBAAoB;EACtQ,MAAM,WAAW,WAAW,IAAI,QAAQ;EACxC,IAAI,aAAa,KAAK,GAAG,MAAM,IAAI,aAAa,mBAAmB,WAAW,QAAQ,EAAE,OAAO,WAAW,IAAI,EAAE,+BAA+B,SAAS,uJAAuJ;EAC/S,WAAW,IAAI,UAAU,IAAI;EAC7B,UAAU,IAAI,MAAM,QAAQ;CAC7B;CACA,OAAO;AACR;;;;;;;;;;;AAWA,SAAS,aAAa,MAAM,QAAQ;CACnC,MAAM,UAAU,KAAK,SAAS,SAAS,GAAG,OAAO,WAAW;CAC5D,OAAO,KAAK,YAAY,YAAY,aAAa,IAAI,IAAI,UAAU,GAAG,QAAQ;AAC/E;;AAEA,SAAS,KAAK,KAAK,MAAM,UAAU;CAClC,OAAO,SAAS,QAAQ,GAAG,IAAI,WAAW,MAAM,GAAG,IAAI,KAAK;AAC7D;;;;;;;;;;;;;;;;;;;;;;;AAuBA,SAAS,gBAAgB,YAAY;CACpC,MAAM,2BAA2B,IAAI,IAAI;CACzC,KAAK,MAAM,CAAC,KAAK,YAAY,OAAO,QAAQ,UAAU,GAAG;EACxD,MAAM,OAAO;EACb,MAAM,SAAS,gBAAgB,IAAI;EACnC,IAAI,QAAQ,MAAM,WAAW,KAAK,QAAQ,OAAO,WAAW,GAAG;EAC/D,SAAS,IAAI,GAAG;EAChB,IAAI,KAAK,YAAY,SAAS,IAAI,KAAK,UAAU;CAClD;CACA,OAAO;AACR;;;;;;;;;;;;;AAaA,SAAS,eAAe,YAAY;CACnC,MAAM,6BAA6B,IAAI,IAAI;CAC3C,KAAK,MAAM,CAAC,KAAK,YAAY,OAAO,QAAQ,UAAU,GAAG;EACxD,MAAM,OAAO;EACb,IAAI,gBAAgB,IAAI,CAAC,EAAE,OAAO,WAAW,GAAG;EAChD,WAAW,IAAI,GAAG;EAClB,IAAI,KAAK,YAAY,WAAW,IAAI,KAAK,UAAU;CACpD;CACA,OAAO;AACR;AACA,SAAS,iBAAiB,OAAO;CAChC,MAAM,cAAc,sBAAsB,KAAK;CAC/C,MAAM,YAAY,eAAe,WAAW;CAC5C,MAAM,QAAQ;EACb;EACA;EACA;EACA;EACA;EACA;CACD;CACA,KAAK,MAAM,cAAc,aAAa;EACrC,MAAM,aAAa,WAAW,cAAc,CAAC;EAC7C,IAAI,oBAAoB,CAAC;EACzB,IAAI;GACH,oBAAoB,2BAA2B,UAAU;EAC1D,SAAS,GAAG;GACX,QAAQ,KAAK,gDAAgD,WAAW,KAAK,4OAA4O,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,GAAG;EACtW;EACA,MAAM,WAAW,0BAA0B,UAAU;EACrD,IAAI,qBAAqB;EACzB,IAAI,UAAU,IAAI;GACjB,qBAAqB,SAAS,CAAC,CAAC;EACjC,QAAQ;GACP,qBAAqB;EACtB;EACA,IAAI,uBAAuB,GAAG,QAAQ,KAAK,aAAa,WAAW,KAAK,aAAa,qBAAqB,IAAI,KAAK,GAAG,mBAAmB,GAAG,8GAA8G,WAAW,KAAK,2IAA2I;EACrZ,MAAM,KAAK,KAAK,QAAQ,UAAU,IAAI,WAAW,IAAI,CAAC,EAAE,IAAI;EAC5D,MAAM,KAAK,YAAY;EACvB,MAAM,8BAA8B,IAAI,IAAI;EAC5C,MAAM,WAAW,gBAAgB,UAAU;EAC3C,KAAK,MAAM,OAAO,UAAU,YAAY,IAAI,GAAG;EAC/C,KAAK,MAAM,CAAC,KAAK,YAAY,OAAO,QAAQ,UAAU,GAAG;GACxD,MAAM,OAAO;GACb,IAAI,KAAK,SAAS,YAAY;GAC9B,IAAI,SAAS,IAAI,GAAG,GAAG;GACvB,MAAM,SAAS,yBAAyB,IAAI;GAC5C,MAAM,aAAa,QAAQ,KAAK,YAAY,QAAQ,KAAK,aAAa,IAAI;GAC1E,MAAM,KAAK,KAAK,KAAK,aAAa,SAAS,GAAG,OAAO,UAAU,CAAC,UAAU,CAAC;GAC3E,YAAY,IAAI,GAAG;EACpB;EACA,KAAK,MAAM,CAAC,QAAQ,aAAa,OAAO,QAAQ,iBAAiB,GAAG,IAAI,SAAS,SAAS,eAAe,SAAS,UAAU;GAC3H,MAAM,QAAQ,kBAAkB,YAAY,SAAS,QAAQ;GAC7D,IAAI,YAAY,IAAI,KAAK,GAAG;GAC5B,MAAM,SAAS,eAAe,QAAQ;GACtC,MAAM,oBAAoB,WAAW;GACrC,MAAM,SAAS,oBAAoB,GAAG,OAAO,KAAK,qBAAqB,UAAU,SAAS,MAAM;GAChG,MAAM,aAAa,mBAAmB,YAAY,QAAQ,KAAK,CAAC;GAChE,MAAM,KAAK,KAAK,OAAO,aAAa,SAAS,GAAG,OAAO,UAAU,CAAC,UAAU,CAAC;GAC7E,YAAY,IAAI,KAAK;EACtB;EACA,KAAK,MAAM,CAAC,KAAK,aAAa,OAAO,QAAQ,iBAAiB,GAAG;GAChE,IAAI,YAAY,IAAI,GAAG,GAAG;GAC1B,MAAM,KAAK,KAAK,KAAK,qBAAqB,UAAU,SAAS,GAAG,IAAI,CAAC;GACrE,YAAY,IAAI,GAAG;EACpB;EACA,KAAK,MAAM,CAAC,KAAK,YAAY,OAAO,QAAQ,UAAU,GAAG;GACxD,IAAI,QAAQ,SAAS,YAAY;GACjC,IAAI,YAAY,IAAI,GAAG,GAAG;GAC1B,MAAM,KAAK,KAAK,KAAK,2BAA2B,IAAI,CAAC;GACrD,YAAY,IAAI,GAAG;EACpB;EACA,MAAM,KAAK,QAAQ;EACnB,MAAM,KAAK,eAAe;EAC1B,MAAM,8BAA8B,IAAI,IAAI,CAAC,GAAG,UAAU,GAAG,eAAe,UAAU,CAAC,CAAC;EACxF,YAAY,MAAM;EAClB,KAAK,MAAM,OAAO,aAAa,YAAY,IAAI,GAAG;EAClD,KAAK,MAAM,CAAC,KAAK,YAAY,OAAO,QAAQ,UAAU,GAAG;GACxD,MAAM,OAAO;GACb,IAAI,KAAK,SAAS,YAAY;GAC9B,IAAI,YAAY,IAAI,GAAG,GAAG;GAC1B,MAAM,SAAS,yBAAyB,IAAI;GAC5C,MAAM,aAAa,CAAC,KAAK,YAAY,YAAY,iBAAiB,IAAI,KAAK,QAAQ,KAAK,eAAe;GACvG,MAAM,KAAK,KAAK,KAAK,aAAa,MAAM,MAAM,GAAG,UAAU,CAAC;GAC5D,YAAY,IAAI,GAAG;EACpB;EACA,sBAAsB,OAAO,YAAY,YAAY,mBAAmB,aAAa,KAAK;EAC1F,MAAM,KAAK,QAAQ;EACnB,MAAM,KAAK,eAAe;EAC1B,YAAY,MAAM;EAClB,KAAK,MAAM,OAAO,aAAa,YAAY,IAAI,GAAG;EAClD,KAAK,MAAM,CAAC,KAAK,YAAY,OAAO,QAAQ,UAAU,GAAG;GACxD,MAAM,OAAO;GACb,IAAI,KAAK,SAAS,YAAY;GAC9B,IAAI,aAAa,IAAI,GAAG;GACxB,IAAI,YAAY,IAAI,GAAG,GAAG;GAC1B,MAAM,KAAK,KAAK,KAAK,aAAa,MAAM,yBAAyB,IAAI,CAAC,GAAG,IAAI,CAAC;GAC9E,YAAY,IAAI,GAAG;EACpB;EACA,sBAAsB,OAAO,YAAY,YAAY,mBAAmB,aAAa,IAAI;EACzF,MAAM,KAAK,QAAQ;EACnB,IAAI,OAAO,KAAK,iBAAiB,CAAC,CAAC,WAAW,GAAG,MAAM,KAAK,sCAAsC;OAC7F,MAAM,KAAK,kBAAkB;EAClC,KAAK,MAAM,CAAC,KAAK,aAAa,OAAO,QAAQ,iBAAiB,GAAG;GAChE,MAAM,OAAO,wBAAwB,QAAQ,CAAC,EAAE,QAAQ,SAAS;GACjE,MAAM,WAAW,OAAO,UAAU,IAAI,IAAI,IAAI,KAAK;GACnD,MAAM,KAAK,SAAS,QAAQ,GAAG,EAAE,IAAI,WAAW,WAAW,QAAQ,IAAI,QAAQ,EAAE;EAClF;EACA,IAAI,OAAO,KAAK,iBAAiB,CAAC,CAAC,SAAS,GAAG,MAAM,KAAK,QAAQ;EAClE,MAAM,KAAK,aAAa,WAAW,WAAW,IAAI,EAAE,EAAE;EACtD,MAAM,KAAK,MAAM;CAClB;CACA,MAAM,KAAK,GAAG;CACd,MAAM,KAAK,EAAE;CACb,MAAM,KAAK,8CAA8C;CACzD,MAAM,KAAK,EAAE;CACb,MAAM,KAAK,wCAAwC;CACnD,KAAK,MAAM,cAAc,aAAa,MAAM,KAAK,KAAK,QAAQ,UAAU,IAAI,WAAW,IAAI,CAAC,EAAE,IAAI,WAAW,WAAW,IAAI,EAAE,EAAE;CAChI,MAAM,KAAK,aAAa;CACxB,MAAM,KAAK,EAAE;CACb,MAAM,KAAK,mEAAmE;CAC9E,MAAM,KAAK,EAAE;CACb,MAAM,KAAK,GAAG,mBAAmB,CAAC;CAClC,OAAO,MAAM,KAAK,IAAI;AACvB;;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,SAAS,qBAAqB;CAC7B,OAAO;EACN;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACD;AACD;;;;;;;;;;;;;;;;;;AAkBA,SAAS,sBAAsB,OAAO,YAAY,YAAY,mBAAmB,aAAa,aAAa;CAC1G,MAAM,QAAQ,KAAK,aAAa;EAC/B,IAAI,YAAY,IAAI,GAAG,GAAG;EAC1B,MAAM,WAAW,mBAAmB,YAAY,QAAQ;EACxD,MAAM,OAAO,WAAW,eAAe,QAAQ,IAAI,GAAG,eAAe,QAAQ,EAAE;EAC/E,MAAM,KAAK,KAAK,KAAK,MAAM,eAAe,CAAC,QAAQ,CAAC;EACpD,YAAY,IAAI,GAAG;CACpB;CACA,KAAK,MAAM,YAAY,OAAO,OAAO,iBAAiB,GAAG,IAAI,SAAS,SAAS,eAAe,SAAS,UAAU,KAAK,kBAAkB,YAAY,SAAS,QAAQ,GAAG,QAAQ;CAChL,KAAK,MAAM,CAAC,KAAK,YAAY,OAAO,QAAQ,UAAU,GAAG;EACxD,IAAI,QAAQ,SAAS,YAAY;EACjC,MAAM,WAAW,aAAa,mBAAmB,GAAG;EACpD,IAAI,UAAU,SAAS,eAAe,SAAS,UAAU,KAAK,KAAK,QAAQ;CAC5E;CACA,KAAK,MAAM,CAAC,KAAK,aAAa,OAAO,QAAQ,iBAAiB,GAAG;EAChE,IAAI,SAAS,gBAAgB,UAAU,CAAC,SAAS,UAAU;EAC3D,IAAI,YAAY,IAAI,GAAG,GAAG;EAC1B,MAAM,KAAK,eAAe,QAAQ;EAClC,MAAM,QAAQ,SAAS,SAAS,eAAe,UAAU,SAAS,QAAQ,YAAY,OAAO,IAAI,KAAK;EACtG,MAAM,UAAU,GAAG,GAAG,WAAW,KAAK,QAAQ,cAAc,UAAU,GAAG;EACzE,MAAM,KAAK,KAAK,KAAK,SAAS,QAAQ,IAAI,IAAI,CAAC;EAC/C,YAAY,IAAI,GAAG;CACpB;AACD;;;;;;AAMA,SAAS,UAAU,iBAAiB,WAAW;CAC9C,MAAM,UAAU,OAAO,QAAQ,eAAe;CAC9C,IAAI,QAAQ,WAAW,GAAG,OAAO,KAAK;CACtC,OAAO,KAAK,QAAQ,KAAK,CAAC,KAAK,aAAa;EAC3C,MAAM,OAAO;EACb,MAAM,SAAS,yBAAyB,IAAI;EAC5C,MAAM,WAAW,QAAQ,KAAK,YAAY,QAAQ;EAClD,MAAM,OAAO,cAAc,UAAU,aAAa,MAAM,MAAM,IAAI,WAAW,SAAS,GAAG,OAAO;EAChG,OAAO,GAAG,QAAQ,GAAG,IAAI,YAAY,cAAc,SAAS,KAAK,IAAI,IAAI,KAAK;CAC/E,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE;AACd;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC9eA,IAAM,qBAAqB,UAAkB,SAAmC;CAC5E,IAAI,QAAQ,gBAAgB,QAAQ,OAAO,KAAK,eAAe,UAC3D,OAAO,KAAK;CAEhB,OAAO,YAAY,QAAQ;AAC/B;;;;;;;;;;;;;;;;AAqFA,IAAM,2BAA2D;CAC7D,MAAM;CACN,SAAS;CACT,MAAM;CACN,MAAM;CAEN,MAAM;CACN,UAAU;CACV,SAAS;CACT,QAAQ;CAGR,aAAa;CACb,QAAQ;CACR,WAAW;CACX,MAAM;CACN,iBAAiB;CACjB,SAAS;CACT,SAAS;CACT,aAAa;CACb,MAAM;CACN,MAAM;CACN,MAAM;CACN,OAAO;CAEP,QAAQ;CACR,OAAO;CACP,UAAU;CACV,OAAO;AACX;;;;;;;;;;AAWA,SAAgB,sBAAsB,MAA+B;CAGjE,IAAI,KAAK,SAAS,YAAY,OAAO;CAMrC,MAAM,aAAa;EACf,MAAM;EACN,OAAO;EACP,YAAY,EAAE,YAAY,KAAK;CACnC;CAEA,IAAI;EAEA,OAAO,yBADM,aAAa,cAAc,MAAM,kBAAkB,KAAA,CAChC,CAAA,CAAK;CACzC,QAAQ;EACJ,OAAO;CACX;AACJ;;;;;;;AAUA,eAAsB,gBAAgB,iBAAsD;CACxF,OAAO,6BAA6B,eAAe;AACvD;AAIA,eAAsB,yBAClB,aACA,gBACoB;CACpB,MAAM,SAAwB,CAAC;CAG/B,IAAI,CAAC,KAAG,WAAW,cAAc,GAAG;EAChC,OAAO,KAAK;GACR,UAAU;GACV,UAAU;GACV,SAAS;GACT,KAAK;EACT,CAAC;EACD,OAAO;GAAE,QAAQ;GACzB;EAAO;CACH;CAKA,MAAM,sBAAsB,sBAAsB,WAAW;CAC7D,IAAI,oBAAoB,WAAW,GAC/B,OAAO;EAAE,QAAQ;EACzB;CAAO;CAGH,IAAI;EAMA,MAAM,cAAc,6BAChB,eAAe,mBAAmB,GAClC,MAAM,SAAW,SAAS,gBAAgB,OAAO,CACrD;EAEA,IAAI,YAAY,SAAS,GACrB,OAAO,KAAK;GACR,UAAU;GACV,UAAU;GACV,SAAS,4GACY,+BAA+B,WAAW,EAAE;GACjE,KAAK;EACT,CAAC;CAET,SAAS,KAAc;EACnB,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;EAC/D,OAAO,KAAK;GACR,UAAU;GACV,UAAU;GACV,SAAS,+CAA+C;GACxD,KAAK;EACT,CAAC;CACL;CAEA,OAAO;EAAE,QAAQ,OAAO,WAAW;EACvC;CAAO;AACP;AAEA,eAAsB,sBAClB,aACA,aACoB;CACpB,MAAM,SAAwB,CAAC;CAM/B,IAAI,CAAC,KAAG,WAAW,WAAW,GAAG;EAC7B,OAAO,KAAK;GACR,UAAU;GACV,UAAU;GACV,SAAS;GACT,KAAK;EACT,CAAC;EACD,OAAO;GAAE,QAAQ;GACzB;GACA,eAAe;EAA2B;CACtC;CAEA,IAAI;EACA,MAAM,cAAc,iBAAiB,WAAW;EAChD,MAAM,YAAY,MAAM,SAAW,SAAS,aAAa,OAAO;EAGhE,MAAM,aAAa,MACf,EACK,QAAQ,aAAa,EAAE,CAAC,CACxB,QAAQ,qBAAqB,EAAE,CAAC,CAChC,QAAQ,QAAQ,GAAG,CAAC,CACpB,KAAK;EAEd,IAAI,UAAU,WAAW,MAAM,UAAU,SAAS,GAC9C,OAAO,KAAK;GACR,UAAU;GACV,UAAU;GACV,SAAS;GACT,KAAK;EACT,CAAC;CAET,SAAS,KAAc;EACnB,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;EAO/D,IAAI,eAAe,SAAS,IAAI,SAAS,gBACrC,OAAO,KAAK;GACR,UAAU;GACV,UAAU;GACV,SAAS,6DAA6D;GACtE,KAAK;EACT,CAAC;OAED,OAAO,KAAK;GACR,UAAU;GACV,UAAU;GACV,SAAS,kDAAkD;GAC3D,KAAK;EACT,CAAC;CAET;CAEA,OAAO;EAAE,QAAQ,OAAO,WAAW;EACvC;CAAO;AACP;;;;;;;;;;;;AA+BA,SAAgB,uBACZ,aACA,WACa;CAKb,IAAI,CAJe,YAAY,MAAK,eAChC,OAAO,OAAO,WAAW,cAAc,CAAC,CAAC,CAAC,CACrC,MAAK,aAAa,UAAuC,SAAS,QAAQ,CAE9E,KAAc,UAAU,IAAI,QAAQ,GAAG,OAAO,CAAC;CAEpD,OAAO,CAAC;EACJ,UAAU;EACV,UAAU;EACV,SAAS;EACT,KAAK;CAET,CAAC;AACL;;;;;;;;;;;;;;;AAgBA,eAAsB,kBAClB,MACA,aACsB;CACtB,IAAI,UAAyB;CAC7B,IAAI;EAMA,IAAI,EAAC,MAHiB,KAAK,MACvB,qEACJ,EAAA,CACa,KAAK,EAAE,EAAE,SAAS,OAAO,CAAC;EAIvC,WAAU,MAHQ,KAAK,MACnB,2FACJ,EAAA,CACc,KAAK,EAAE,EAAE,SAAS;CACpC,QAAQ;EAIJ,OAAO,CAAC;CACZ;CAEA,IAAI,CAAC,SAAS,OAAO,CAAC;CACtB,MAAM,WAAW,qBAAqB,WAAW;CACjD,IAAI,YAAY,UAAU,OAAO,CAAC;CAElC,OAAO,CAAC;EACJ,UAAU;EACV,UAAU;EACV,SAAS,4EAA4E,QAAQ,MAAM,GAAG,EAAE,EAAE,4BAA4B,SAAS,MAAM,GAAG,EAAE,EAAE;EAC5J,KAAK;CACT,CAAC;AACL;;AAGA,IAAM,eAAe,QAAgB,UAA0B,IAAI,OAAO,KAAK,MAAM;;;;;;;;AASrF,SAAgB,gBACZ,QACA,OACA,QACA,QACA,SACM;CACN,MAAM,OAAO,sDAAsD,OAAO;CAC1E,IAAI,CAAC,SAAS,OAAO,GAAG,KAAK;CAC7B,MAAM,OAAO,aAAa,OAAO;CACjC,OAAO,GAAG,KAAK,oFACM,YAAY,QAAQ,KAAK,EAAE,iBAAiB,OAAO,SAAS,KAAK,UAAU,OAAO,KAAK,KAAK;AACrH;;;;;;;;;AAUA,SAAgB,iBAAiB,WAAsB,MAAgB,aAAoC;CACvG,MAAM,WAAW,UAAU;CAE3B,IADa,KAAK,WAAW,SAAS,UAAU,CAAC,GAAG,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,IAAQ,MAAM,CAAC,GAAG,QAAQ,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,IAAQ,GAC5G,OAAO,CAAC;CAClB,MAAM,QAAQ,YAAY,UAAU,QAAQ,UAAU,KAAK;CAC3D,MAAM,UAAU,SAAS,KAAI,MAAK,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI;CACrD,IAAI,KAAK,WAAW,GAChB,OAAO,CAAC;EACJ,UAAU;EACV,UAAU;EACV,OAAO;EACP,UAAU,SAAS,KAAK,IAAI;EAC5B,QAAQ;EACR,SAAS,UAAU,YAAY,yDAAyD,QAAQ;EAChG,KAAK,eAAe,MAAM,oBAAoB,QAAQ;CAC1D,CAAC;CAIL,MAAM,OAAO,SAAS,MAAK,WAAU,CAAC,KAAK,SAAS,MAAM,CAAC,IAAI,UAAU;CACzE,MAAM,MAAM,KAAK,WAAW,IACtB,QAAQ,KAAK,2BAA2B,KAAK,GAAG,sEAAsE,QAAQ,KAC9H,QAAQ,KAAK,6BAA6B,KAAK,KAAI,MAAK,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,wFACnB,QAAQ;CACxE,OAAO,CAAC;EACJ,UAAU;EACV,UAAU;EACV,OAAO;EACP,UAAU,SAAS,KAAK,IAAI;EAC5B,QAAQ,KAAK,KAAK,IAAI;EACtB,SAAS,UAAU,YAAY,iBAAiB,KAAK,KAAK,IAAI,EAAE,0CAA0C,QAAQ;EAClH;CACJ,CAAC;AACL;;;;;;AAOA,SAAgB,kBACZ,cACA,WACA,MACA,aACa;CACb,MAAM,SAAwB,CAAC;CAC/B,MAAM,SAAS,IAAI,IAAI,UAAU,KAAI,MAAK,CAAC,EAAE,aAAa,CAAC,CAAC,CAAC;CAC7D,KAAK,MAAM,UAAU,aAAa,SAAS;EACvC,MAAM,QAAQ,OAAO,IAAI,OAAO,MAAM;EACtC,IAAI,CAAC,OAAO;GACR,OAAO,KAAK;IACR,UAAU;IACV,UAAU;IACV,OAAO;IACP,QAAQ,OAAO;IACf,SAAS,mBAAmB,YAAY,YAAY,OAAO,OAAO;IAClE,KAAK;GACT,CAAC;GACD;EACJ;EACA,MAAM,WAAW,yBAAyB,OAAO,KAAK;EACtD,IAAI,MAAM,cAAc,UACpB,OAAO,KAAK;GACR,UAAU;GACV,UAAU;GACV,OAAO;GACP,QAAQ,OAAO;GACf;GACA,QAAQ,MAAM;GACd,SAAS,oBAAoB,OAAO,OAAO,QAAQ,YAAY,OAAO,MAAM,UAAU,gCAAgC,SAAS;GAC/H,KAAK,gBAAgB,aAAa,QAAQ,aAAa,OAAO,OAAO,QAAQ,MAAM,WAAW,OAAO,IAAI;EAC7G,CAAC;CAET;CACA,OAAO,KAAK,GAAG,iBAAiB,cAAc,MAAM,WAAW,CAAC;CAChE,OAAO;AACX;AAEA,eAAsB,2BAClB,aACA,aACoB;CACpB,MAAM,SAAwB,CAAC;CAG/B,MAAM,WAAW,MAAM,OAAO;CAC9B,MAAM,EAAE,SAAS,SAAS,WAAW;CACrC,MAAM,OAAO,IAAI,KAAK,EAAE,kBAAkB,YAAY,CAAC;CAGvD,MAAM,UAAU,MAAM,qBAAK,IAAI,IAAI;EAC/B;EACA;EACA,GAAG,sBAAsB,WAAW,CAAC,CAChC,OAAO,0BAA0B,CAAC,CAClC,KAAI,MAAK,EAAE,MAAM,CAAC,CAClB,QAAQ,MAAmB,CAAC,CAAC,CAAC;CACvC,CAAC,CAAC;CAEF,IAAI;EAEA,MAAM,eAAe,MAAM,KAAK,MAC5B;;0EAGA,CAAC,OAAO,CACZ;EACA,MAAM,iBAAiB,IAAI,IAAI,aAAa,KAAK,KAAK,MAClD,EAAE,iBAAiB,WAAW,EAAE,aAAa,GAAG,EAAE,aAAa,GAAG,EAAE,YACxE,CAAC;EAUD,MAAM,mBAAmB,MAAM,KAAK,MAA2B,kCAAkC;EACjG,MAAM,sBAAsB,IAAI,IAAI,iBAAiB,KAAK,KAAI,MAAK,EAAE,OAAO,CAAC;EAC7E,OAAO,KAAK,GAAG,uBAAuB,aAAa,mBAAmB,CAAC;EAQvE,OAAO,KAAK,GAAG,MAAM,kBAAkB,MAAM,WAAW,CAAC;EAGzD,MAAM,gBAAgB,MAAM,KAAK,MAC7B;;;mEAIA,CAAC,OAAO,CACZ;EACA,MAAM,iCAAiB,IAAI,IAAwB;EACnD,KAAK,MAAM,OAAO,cAAc,MAAM;GAClC,MAAM,cAAc,IAAI;GACxB,MAAM,YAAY,IAAI;GACtB,MAAM,MAAM,gBAAgB,WAAW,YAAY,GAAG,YAAY,GAAG;GACrE,IAAI,CAAC,eAAe,IAAI,GAAG,GACvB,eAAe,IAAI,KAAK,CAAC,CAAC;GAE9B,eAAe,IAAI,GAAG,CAAC,CAAE,KAAK,GAAG;EACrC;EAKA,MAAM,cAAc,MAAM,KAAK,MAC3B;;;;4DAKJ;EACA,MAAM,8BAAc,IAAI,IAAsB;EAC9C,KAAK,MAAM,OAAO,YAAY,MAAM;GAChC,MAAM,MAAM,GAAG,IAAI,YAAY,GAAG,IAAI;GACtC,IAAI,CAAC,YAAY,IAAI,GAAG,GACpB,YAAY,IAAI,KAAK,CAAC,CAAC;GAE3B,YAAY,IAAI,GAAG,CAAC,CAAE,KAAK,IAAI,UAAU;EAC7C;EAGA,MAAM,YAAY,MAAM,KAAK,MACzB;;;;;;;6CAQA,CAAC,OAAO,CACZ;EACA,MAAM,6BAAa,IAAI,IAAsB;EAC7C,KAAK,MAAM,OAAO,UAAU,MACxB,WAAW,IAAI,IAAI,iBAAiB,WAAW,IAAI,aAAa,GAAG,IAAI,aAAa,GAAG,IAAI,cAAc,IAAI,OAAO;EAIxH,MAAM,YAAY,MAAM,KAAK,MASzB;;;;;;;;;;;;;sFAcA,CAAC,OAAO,CACZ;EACA,MAAM,6BAAa,IAAI,IAAmC;EAC1D,KAAK,MAAM,OAAO,UAAU,MAAM;GAC9B,MAAM,cAAc,IAAI;GACxB,MAAM,YAAY,IAAI;GACtB,MAAM,MAAM,gBAAgB,WAAW,YAAY,GAAG,YAAY,GAAG;GACrE,IAAI,CAAC,WAAW,IAAI,GAAG,GACnB,WAAW,IAAI,KAAK,CAAC,CAAC;GAE1B,WAAW,IAAI,GAAG,CAAC,CAAE,KAAK,GAAG;EACjC;EAIA,MAAM,sBAAsB,sBAAsB,WAAW;EAC7D,MAAM,gBAAgB,qBAAqB,mBAAmB;EAM9D,IAAI;EACJ,IAAI;GACA,OAAO,WAAW,mBAAmB;EACzC,QAAQ;GACJ,OAAO,KAAA;EACX;EACA,MAAM,aAAa,IAAI,KAAwB,MAAM,UAAU,CAAC,EAAA,CAAG,KAAI,MAAK,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC;EAC7F,MAAM,mCAAmB,IAAI,IAAY;EAEzC,KAAK,MAAM,cAAc,qBAAqB;GAC1C,MAAM,YAAY,aAAa,UAAU;GACzC,MAAM,aAAc,WAAmC,UAAU;GACjE,MAAM,gBAAgB,eAAe,WAAW,YAAY,GAAG,WAAW,GAAG;GAG7E,IAAI,CAAC,eAAe,IAAI,aAAa,GAAG;IACpC,OAAO,KAAK;KACR,UAAU;KACV,UAAU;KACV,OAAO;KACP,SAAS,UAAU,cAAc;KACjC,KAAK;IACT,CAAC;IACD;GACJ;GAEA,MAAM,YAAY,eAAe,IAAI,aAAa,KAAK,CAAC;GACxD,MAAM,cAAc,IAAI,IAAI,UAAU,KAAK,MAAM,CAAC,EAAE,aAAa,CAAC,CAAC,CAAC;GAKpE,MAAM,YAAY,WAAW,IAAI,GAAG,WAAW,GAAG,WAAW;GAC7D,IAAI,WACA,OAAO,KAAK,GAAG,iBAAiB,WAAW,WAAW,IAAI,aAAa,KAAK,CAAC,GAAG,aAAa,CAAC;GAIlG,KAAK,MAAM,CAAC,UAAU,SAAS,OAAO,QAAQ,WAAW,cAAc,CAAC,CAAC,GAAG;IACxE,IAAI,KAAK,SAAS,YAAY;KAG1B,MAAM,WAAW,aADS,2BAA2B,UACvB,GAAoB,KAA0B,UAAU,gBAAgB,QAAQ;KAC9G,IAAI,UAAU,SAAS,aAAa;MAChC,MAAM,YAAY,SAAS;MAC3B,IAAI,CAAC,YAAY,IAAI,SAAS,GAC1B,OAAO,KAAK;OACR,UAAU;OACV,UAAU;OACV,OAAO;OACP,QAAQ;OACR,SAAS,uBAAuB,UAAU,kBAAkB,SAAS,2BAA2B,cAAc;OAC9G,KAAK;MACT,CAAC;MAIL,MAAM,WAAW,WAAW,IAAI,aAAa,KAAK,CAAC;MACnD,IAAI,kBAAkB;MACtB,IAAI,mBAAmB;MACvB,IAAI;OACA,MAAM,aAAa,SAAS,OAAO;OACnC,kBAAkB,aAAa,UAAU;OACzC,mBAAoB,WAAmC,UAAU;MACrE,QAAQ,CAAe;MAEvB,MAAM,QAAQ,SAAS,MAAM,OACzB,GAAG,gBAAgB,aACnB,GAAG,uBAAuB,mBAC1B,GAAG,yBAAyB,gBAChC;MAEA,IAAI,YAAY,IAAI,SAAS,KAAK,CAAC,OAC/B,OAAO,KAAK;OACR,UAAU;OACV,UAAU;OACV,OAAO;OACP,QAAQ;OACR,SAAS,WAAW,UAAU,0DAA0D,qBAAqB,WAAW,kBAAkB,GAAG,iBAAiB,GAAG,kBAAkB;OACnL,KAAK;MACT,CAAC;KAET;KACA;IACJ;IAEA,MAAM,UAAU,kBAAkB,UAAU,IAAI;IAEhD,MAAM,QAAQ,YAAY,IAAI,OAAO;IACrC,IAAI,CAAC,OAAO;KACR,OAAO,KAAK;MACR,UAAU;MACV,UAAU;MACV,OAAO;MACP,QAAQ;MACR,SAAS,WAAW,QAAQ,8BAA8B,WAAW,KAAK,4BAA4B,cAAc;MACpH,KAAK;KACT,CAAC;KACD;IACJ;IAGA,MAAM,eAAe,sBAAsB,IAAI;IAC/C,IAAI,cAAc;KACd,MAAM,aAAa,MAAM;KACzB,IAAI,aAAa,eAAe;KAChC,IAAI,KAAK,SAAS,YAAY,MAAM,aAAa,UAC7C,aAAa;KAEjB,IAAI,KAAK,SAAS,SAAS;MACvB,MAAM,KAAK;MACX,IAAI,kBAAkB,GAAG;MACzB,IAAI,CAAC,mBAAmB,GAAG,MAAM,CAAC,MAAM,QAAQ,GAAG,EAAE,GAAG;OACpD,MAAM,SAAS,GAAG;OAClB,IAAI,OAAO,SAAS,UAAU,kBAAkB;YAC3C,IAAI,OAAO,SAAS,UAAU,kBAAkB,OAAO,YAAY,UAAU,cAAc;YAC3F,IAAI,OAAO,SAAS,WAAW,kBAAkB;MAC1D;MACA,IAAI,mBAAmB,gBAAgB,SAAS,IAAI,GAChD,IAAI,eAAe,SACf,aAAa;WACV;OACH,MAAM,cAAc,oBAAoB,WAAW,UAC/B,oBAAoB,cAAc,UAClC,oBAAoB,cAAc,UAClC,oBAAoB,cAAc,aAAa;OACnE,IAAI,eAAe,MAAM,aAAa,aAClC,aAAa;MAErB;KAER;KACA,IAAI,YAAY;MACZ,MAAM,UAAU,WAAW,QAAQ,MAAK,MAAK,EAAE,WAAW,OAAO,CAAC,EAAE;MACpE,OAAO,KAAK;OACR,UAAU;OACV,UAAU;OACV,OAAO;OACP,QAAQ;OACR,UAAU,KAAK,SAAS,WAAW,WAAW;OAC9C,QAAQ,MAAM,aAAa,WAAW,WAAW;OACjD,SAAS,WAAW,QAAQ,cAAc,cAAc,oBAAoB,KAAK,SAAS,WAAW,WAAW,aAAa,eAAe,MAAM,aAAa,WAAW,WAAW,WAAW;OAChM,KAAK,gBAAgB,YAAY,WAAW,SAAS,YAAY,OAAO;MAC5E,CAAC;KACL;IACJ;IAGA,IAAI,KAAK,SAAS,YAAa,KAAwB,MAAM;KACzD,MAAM,aAAc,KAAwB;KAC5C,IAAI,YAAY;MAGZ,MAAM,WAAW,qBAAqB,GAAG,UAAU,GAAG,SAAS;MAC/D,MAAM,eAAe,YAAY,IAAI,GAAG,WAAW,GAAG,UAAU;MAChE,IAAI,CAAC,cACD,OAAO,KAAK;OACR,UAAU;OACV,UAAU;OACV,OAAO;OACP,QAAQ;OACR,UAAU;OACV,SAAS,cAAc,WAAW,KAAK,SAAS;OAChD,KAAK;MACT,CAAC;WACE;OAEH,MAAM,iBAAiB,MAAM,QAAQ,UAAU,IACzC,WAAW,KAAK,MAAO,OAAO,MAAM,WAAW,IAAI,OAAO,EAAE,EAAE,CAAE,IAChE,OAAO,KAAK,UAAU;OAE5B,MAAM,UAAU,eAAe,QAAQ,MAAM,CAAC,aAAa,SAAS,CAAC,CAAC;OACtE,MAAM,QAAQ,aAAa,QAAQ,MAAM,CAAC,eAAe,SAAS,CAAC,CAAC;OAEpE,IAAI,QAAQ,SAAS,KAAK,MAAM,SAAS,GAAG;QACxC,MAAM,QAAkB,CAAC;QACzB,IAAI,QAAQ,SAAS,GAAG,MAAM,KAAK,YAAY,QAAQ,KAAK,IAAI,GAAG;QACnE,IAAI,MAAM,SAAS,GAAG,MAAM,KAAK,gBAAgB,MAAM,KAAK,IAAI,GAAG;QACnE,OAAO,KAAK;SACR,UAAU;SACV,UAAU;SACV,OAAO;SACP,QAAQ;SACR,UAAU,eAAe,KAAK,IAAI;SAClC,QAAQ,aAAa,KAAK,IAAI;SAC9B,SAAS,oBAAoB,QAAQ,cAAc,cAAc,qBAAqB,MAAM,KAAK,IAAI,EAAE;SACvG,KAAK,MAAM,SAAS,KAAK,QAAQ,WAAW,IAEtC,sEAAsE,MAAM,KAAK,IAAI,EAAE,uPACvF,oDAAoD,MAAM,SAAS,IAAI,sBAAsB,MAAM,KAAK,IAAI,EAAE,uGAAuG;QAC/N,CAAC;OACL;MACJ;KACJ;IACJ;GACJ;GAMA,MAAM,oBAAoB,2BAA2B,UAAU;GAC/D,KAAK,MAAM,YAAY,OAAO,OAAO,iBAAiB,GAClD,IAAI,aAAa,QAAQ,GAAG;IACxB,MAAM,gBAAgB,SAAS,QAAQ,MAAM,MAAM,GAAG,CAAC,CAAC,IAAI,KAAK,SAAS,QAAQ;IAClF,MAAM,iBAAiB,cAAc,IAAI,aAAa,CAAC,EAAE,UAAU;IACnE,MAAM,oBAAoB,mBAAmB,WAAW,gBAAgB,GAAG,eAAe,GAAG;IAC7F,IAAI,CAAC,eAAe,IAAI,iBAAiB,GACrC,OAAO,KAAK;KACR,UAAU;KACV,UAAU;KACV,OAAO;KACP,SAAS,mBAAmB,kBAAkB,+BAA+B,SAAS,aAAa;KACnG,KAAK;IACT,CAAC;SACE,IAAI,CAAC,iBAAiB,IAAI,iBAAiB,GAAG;KAEjD,iBAAiB,IAAI,iBAAiB;KACtC,MAAM,eAAe,WAAW,IAAI,GAAG,eAAe,GAAG,eAAe;KACxE,IAAI,cACA,OAAO,KAAK,GAAG,kBACX,cACA,eAAe,IAAI,iBAAiB,KAAK,CAAC,GAC1C,WAAW,IAAI,iBAAiB,KAAK,CAAC,GACtC,iBACJ,CAAC;IAET;GACJ;EAER;EAEA,IAAI,MACA,OAAO,KAAK,GAAG,uBAAuB,MAAM,gBAAgB,YAAY,UAAU,CAAC;CAE3F,UAAU;EACN,MAAM,KAAK,IAAI;CACnB;CAGA,OAAO;EAAE,QAAQ,CAAC,OAAO,MAAK,UAAS,MAAM,aAAa,MAAM;EACpE;CAAO;AACP;;;;;;;;;;;;AAaA,SAAgB,uBACZ,MACA,gBACA,YACA,YACa;CACb,MAAM,WAAW,IAAI,IAAI,KAAK,OAAO,KAAI,UAAS,MAAM,SAAS,CAAC;CAClE,MAAM,mBAAmB,IAAI,IAAI,KAAK,OAAO,QAAO,UAAS,MAAM,SAAS,YAAY,CAAC,CAAC,KAAI,UAAS,MAAM,SAAS,CAAC;CACvH,MAAM,SAAwB,CAAC;CAC/B,KAAK,MAAM,CAAC,OAAO,YAAY,gBAAgB;EAE3C,IAAI,MAAM,SAAS,GAAG,KAAK,SAAS,IAAI,UAAU,OAAO,GAAG;EAE5D,IAAI,QAAQ,WAAW,MAAM,WAAW,IAAI,KAAK,KAAK,CAAC,EAAA,CAAG,WAAW,GAAG;EAExE,MAAM,+BAAe,IAAI,IAAkD;EAC3E,KAAK,MAAM,MAAM,WAAW,IAAI,KAAK,KAAK,CAAC,GAAG;GAC1C,MAAM,SAAS,GAAG,GAAG,qBAAqB,GAAG,GAAG;GAChD,aAAa,IAAI,GAAG,iBAAiB,CAAC,GAAI,aAAa,IAAI,GAAG,eAAe,KAAK,CAAC,GAAI;IAAE,QAAQ,GAAG;IAAa;GAAO,CAAC,CAAC;EAC9H;EACA,MAAM,QAAQ,CAAC,GAAG,aAAa,OAAO,CAAC;EACvC,IAAI,MAAM,WAAW,KAAK,MAAM,MAAK,SAAQ,KAAK,WAAW,CAAC,GAAG;EACjE,IAAI,IAAI,IAAI,MAAM,KAAK,CAAC,UAAU,KAAK,MAAM,CAAC,CAAC,CAAC,SAAS,GAAG;EAC5D,IAAI,CAAC,MAAM,OAAO,CAAC,UAAU,iBAAiB,IAAI,KAAK,MAAM,CAAC,GAAG;EAEjE,MAAM,UAAU,MAAM,KAAK,CAAC,UAAU,KAAK,OAAO,QAAQ,aAAa,EAAE,CAAC;EAC1E,OAAO,KAAK;GACR,UAAU;GACV,UAAU;GACV;GACA,SAAS,UAAU,MAAM,WAAW,QAAQ,GAAG,SAAS,QAAQ,GAAG;GACnE,KAAK,wBAAwB,MAAM;EACvC,CAAC;CACL;CACA,OAAO;AACX;AAIA,SAAgB,aAAa,QAA4B;CACrD,IAAI;CACJ,IAAI,MAAM,KAAK,2BAA2B,CAAC;CAC3C,IAAI,MAAM,KAAK,oDAAoD,CAAC;CACpE,IAAI;CAGJ,YAAY,kCAAkC,OAAO,mBAAmB;CAGxE,YAAY,0BAA0B,OAAO,gBAAgB;CAG7D,YAAY,2BAA2B,OAAO,gBAAgB;CAG9D,IAAI,MAAM,KAAK,oDAAoD,CAAC;CACpE,MAAM,EAAE,QAAQ,SAAS,eAAe,UAAU,WAAW,OAAO;CAEpE,MAAM,QAAkB,CAAC;CACzB,MAAM,KAAK,MAAM,MAAM,GAAG,OAAO,QAAQ,CAAC;CAC1C,IAAI,UAAU,GAAG,MAAM,KAAK,MAAM,OAAO,GAAG,QAAQ,SAAS,CAAC;CAC9D,IAAI,gBAAgB,GAAG,MAAM,KAAK,MAAM,KAAK,GAAG,cAAc,gBAAgB,CAAC;CAC/E,IAAI,WAAW,GAAG,MAAM,KAAK,MAAM,OAAO,GAAG,SAAS,UAAU,CAAC;CACjE,IAAI,SAAS,GAAG,MAAM,KAAK,MAAM,IAAI,GAAG,OAAO,QAAQ,CAAC;CAExD,IAAI,cAAc,MAAM,KAAK,IAAI,GAAG;CACpC,IAAI;CAEJ,IAAI,SAAS,GACT,IAAI,MAAM,IAAI,KAAK,2DAA2D,CAAC;MAC5E,IAAI,WAAW,GAClB,IAAI,MAAM,OAAO,KAAK,kEAAkE,CAAC;MACtF,IAAI,UAAU,GAGjB,IAAI,MAAM,OAAO,KAAK,iDAAiD,QAAQ,mDAAmD,CAAC;MAEnI,IAAI,MAAM,MAAM,KAAK,8BAA8B,CAAC;CAExD,IAAI;AACR;AAEA,SAAS,YAAY,OAAe,OAA0B;CAI1D,IAAI,MAAM,SAAS;EACf,IAAI,KAAK,MAAM,OAAO,GAAG,EAAE,IAAI,MAAM,IAAI,MAAM,OAAO,YAAY,MAAM,QAAQ,EAAE,GAAG;EACrF,IAAI;EACJ;CACJ;CAKA,IAAI,MAAM,eAAe;EACrB,IAAI,KAAK,MAAM,KAAK,GAAG,EAAE,GAAG,MAAM,IAAI,MAAM,KAAK,MAAM,aAAa,GAAG;EACvE,KAAK,MAAM,SAAS,MAAM,OAAO,QAAQ,MAAM,EAAE,aAAa,MAAM,GAChE,IAAI,QAAQ,MAAM,KAAK,MAAM,GAAG,GAAG;EAEvC,IAAI;EACJ;CACJ;CAEA,MAAM,SAAS,MAAM;CACrB,MAAM,aAAa,OAAO,QAAQ,MAAM,EAAE,aAAa,OAAO,CAAC,CAAC;CAChE,MAAM,YAAY,OAAO,QAAQ,MAAM,EAAE,aAAa,SAAS,CAAC,CAAC;CACjE,MAAM,aAAa,OAAO,QAAQ,MAAM,EAAE,aAAa,MAAM;CAI7D,IAAI,eAAe,KAAK,cAAc,GAClC,IAAI,KAAK,MAAM,MAAM,GAAG,EAAE,GAAG,MAAM,IAAI,MAAM,MAAM,SAAS,GAAG;MAC5D;EACH,MAAM,QAAkB,CAAC;EACzB,IAAI,aAAa,GAAG,MAAM,KAAK,GAAG,WAAW,QAAQ,aAAa,IAAI,MAAM,IAAI;EAChF,IAAI,YAAY,GAAG,MAAM,KAAK,GAAG,UAAU,UAAU,YAAY,IAAI,MAAM,IAAI;EAC/E,IAAI,KAAK,MAAM,OAAO,IAAI,EAAE,IAAI,MAAM,IAAI,MAAM,OAAO,MAAM,KAAK,IAAI,CAAC,GAAG;CAC9E;CACA,IAAI;CAGJ,KAAK,MAAM,SAAS,YAAY;EAC5B,MAAM,UAAU,MAAM,MAAM,MAAM,KAAK,MAAM,MAAM,KAAK,IAAI;EAC5D,IAAI,KAAK,MAAM,KAAK,GAAG,EAAE,GAAG,MAAM,KAAK,MAAM,OAAO,IAAI,SAAS;EACjE,IAAI;CACR;CAEA,KAAK,MAAM,SAAS,OAAO,QAAQ,MAAM,EAAE,aAAa,MAAM,GAAG;EAC7D,MAAM,eAAe,MAAM,aAAa,UAAU,MAAM,IAAI,GAAG,IAAI,MAAM,OAAO,GAAG;EACnF,MAAM,gBAAgB,eAAe,MAAM,QAAQ;EACnD,IAAI,KAAK,MAAM,KAAK,IAAI,EAAE,GAAG,aAAa,GAAG,MAAM,KAAK,aAAa,EAAE,GAAG,MAAM,KAAK,IAAI,OAAO,KAAK,IAAI,GAAG,KAAK,cAAc,MAAM,CAAC,CAAC,GAAG;EAE1I,IAAI,MAAM,OAAO;GACb,MAAM,UAAU,MAAM,SAAS,cAAc,MAAM,KAAK,MAAM,MAAM,MAAM;GAC1E,IAAI,KAAK,MAAM,KAAK,GAAG,EAAE,UAAU,MAAM,KAAK,MAAM,KAAK,IAAI,SAAS;EAC1E;EAEA,IAAI,MAAM,YAAY,MAAM,QACxB,IAAI,KAAK,MAAM,KAAK,GAAG,EAAE,aAAa,MAAM,MAAM,MAAM,QAAQ,EAAE,aAAa,MAAM,IAAI,MAAM,MAAM,GAAG;EAG5G,IAAI,KAAK,MAAM,KAAK,GAAG,EAAE,GAAG,MAAM,SAAS;EAC3C,IAAI,KAAK,MAAM,KAAK,GAAG,EAAE,QAAQ,MAAM,KAAK,MAAM,GAAG,GAAG;EACxD,IAAI,KAAK,MAAM,KAAK,MAAM,IAAI,OAAO,EAAE,CAAC,GAAG;EAC3C,IAAI;CACR;AACJ;AAEA,SAAS,eAAe,KAAsC;CAgB1D,OAAO;EAdH,eAAe;EACf,gBAAgB;EAChB,eAAe;EACf,oBAAoB;EACpB,sBAAsB;EACtB,cAAc;EACd,cAAc;EACd,qBAAqB;EACrB,qBAAqB;EACrB,mBAAmB;EACnB,WAAW;EACX,mBAAmB;EACnB,mBAAmB;CAEhB,EAAO;AAClB;AAIA,eAAsB,UAAU,SAKN;CACtB,IAAI;CACJ,IAAI,MAAM,KAAK,6BAA6B,CAAC;CAC7C,MAAM,cAAc,MAAM,gBAAgB,QAAQ,eAAe;CACjE,IAAI,YAAY,WAAW,GAAG;EAC1B,SAAS,MAAM,IAAI,2BAA2B,CAAC;EAC/C,QAAQ,KAAK,CAAC;CAClB;CACA,IAAI,MAAM,KAAK,WAAW,YAAY,OAAO,eAAe,CAAC;CAC7D,IAAI;CAGJ,IAAI,MAAM,KAAK,8CAA8C,CAAC;CAC9D,MAAM,sBAAsB,MAAM,yBAAyB,aAAa,QAAQ,UAAU;CAG1F,IAAI;CACJ,IAAI,QAAQ,aAAa;EACrB,IAAI,MAAM,KAAK,sCAAsC,CAAC;EACtD,IAAI;GACA,mBAAmB,MAAM,2BAA2B,aAAa,QAAQ,WAAW;EACxF,SAAS,KAAc;GAOnB,MAAM,SAAS,gBAAgB,KAAK,QAAQ,WAAW;GACvD,IAAI,QAAQ,SAAS,MAAM;GAC3B,MAAM,SAAS,oBAAoB,GAAG,MAC9B,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;GACvD,mBAAmB;IACf,QAAQ;IACR,QAAQ,CAAC;IACT,SAAS,mBAAmB,cAAc,QAAQ,WAAW,EAAE,KAAK;IACpE,SAAS;GACb;EACJ;CACJ,OAAO;EAEH,mBAAmB;GAAE,QAAQ;GACrC,QAAQ,CAAC;GACT,SAAS;EAAuB;EACxB,IAAI,MAAM,OAAO,0DAA0D,CAAC;EAC5E,IAAI,MAAM,KAAK,mEAAmE,CAAC;CACvF;CAGA,IAAI,MAAM,KAAK,uCAAuC,CAAC;CACvD,MAAM,mBAAmB,MAAM,sBAAsB,aAAa,QAAQ,OAAO;CAEjF,MAAM,SAAS;EAAC;EAAqB;EAAkB;CAAgB;CACvE,MAAM,YAAY,OAAO,SAAS,MAAM,EAAE,MAAM;CAChD,MAAM,UAAU;EAIZ,QAAQ,OAAO,QAAQ,MAAM,EAAE,UAAU,CAAC,EAAE,WAAW,CAAC,EAAE,aAAa,CAAC,CAAC;EACzE,SAAS,OAAO,QAAQ,MAAM,EAAE,OAAO,CAAC,CAAC;EACzC,eAAe,OAAO,QAAQ,MAAM,EAAE,iBAAiB,CAAC,EAAE,OAAO,CAAC,CAAC;EACnE,UAAU,UAAU,QAAQ,MAAM,EAAE,aAAa,SAAS,CAAC,CAAC;EAC5D,QAAQ,UAAU,QAAQ,MAAM,EAAE,aAAa,OAAO,CAAC,CAAC;CAC5D;CAEA,MAAM,SAAuB;EAAE;EACnC;EACA;EACA;CAAQ;CACJ,aAAa,MAAM;CAEnB,OAAO;AACX"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ensure-collection-policies-C1ux3V_z.js","names":[],"sources":["../src/schema/ensure-collection-policies.ts"],"sourcesContent":["/**\n * Applying a bundle's RLS policies to a database at boot, idempotently.\n *\n * ## Why this exists\n *\n * {@link ensureCollectionTables} creates the collection *tables* a managed\n * runtime boots against, but a table with row-level security disabled and no\n * policies is not servable: authenticated requests run as the restricted\n * `rebase_user` role, so a read with no `SELECT` policy returns nothing (a\n * public collection answered 401) and a write with no `INSERT`/`UPDATE` policy\n * is denied. The policies live in the collections' `securityRules`; nothing at\n * boot applied them. `rebase db push` does — but it drives Atlas against a\n * local `DATABASE_URL`, and a managed tenant's database is reachable only from\n * inside the cluster, by the runtime that is already connected to it. So the\n * runtime is the only thing that *can* apply them, and this is where it does.\n *\n * ## Why this is safe to run on every boot\n *\n * Every statement is idempotent: `ENABLE ROW LEVEL SECURITY` is only issued for\n * a table the catalogue reports RLS-off (on one that has it, it changes nothing\n * and still queues for an ACCESS EXCLUSIVE lock), and each policy is a `DROP\n * POLICY IF EXISTS` immediately followed by a `CREATE POLICY`, so re-applying\n * asserts exactly the declared state. It adds\n * and replaces; it never drops data. (It does not *reconcile* — a policy a\n * previous push left behind under an old name is not removed here; that stays a\n * `db push` / `db migrate` concern, alongside destructive schema changes.)\n *\n * ## A table whose policies are already the declared ones is left alone\n *\n * Every policy this module writes carries a stamp, `COMMENT ON POLICY … IS\n * 'rebase:<hash>'` (see {@link policyStampSql}). The hash covers the\n * `CREATE POLICY` statement that was rendered *and* the definition Postgres\n * stored for it — command, mode, roles, and the deparsed `USING` / `WITH\n * CHECK` — as read back in the same transaction. A boot recomputes it from the\n * statement it would run now and the definition the catalogue holds now, and\n * re-applies a table only when that differs for any of its policies, a declared\n * policy is missing, a superseded one is still there, or RLS is off.\n *\n * So both halves of \"the declared state\" still win: a rule whose compiled SQL\n * changed (a new rule under the same name, a compiler change) misses on the\n * first half, and a policy someone altered by hand — `ALTER POLICY`, a\n * different role list — misses on the second, and either is re-applied exactly\n * as every boot used to re-apply everything. A boot that changes nothing runs\n * two catalogue reads here, no DDL and takes no table lock. It used to drop and\n * re-create every policy, one ACCESS EXCLUSIVE lock per table: about 1 s at 200\n * collections, and under a `pg_dump` up to the whole retry budget.\n *\n * A policy without a stamp — written by `db push`, by hand, or by a release\n * that predates stamps — counts as different, so the first boot after either\n * re-applies its table once and stamps it. Nothing else reads the comment:\n * drift checks, `doctor`, rls-check and Studio compare names, roles, commands\n * and clauses, never descriptions.\n *\n * Unlike table creation, a failure here is not fatal: RLS stays enabled, so a\n * table whose policies could not be applied fails **closed** (denies) rather\n * than leaking rows. One collection's policy failing (e.g. a rule that\n * references a table a real migration has not created yet) must not crash-loop\n * the whole deployment and take the other collections' working routes down with\n * it. Failures are reported loudly and per-table so the operator can see\n * exactly which collection is not yet servable and why.\n */\nimport { createHash } from \"node:crypto\";\nimport { type CollectionConfig } from \"@rebasepro/types\";\nimport { planCollectionPolicies, type CollectionPolicyPlan } from \"./generate-postgres-ddl-logic\";\nimport { isGeneratedPolicyName } from \"../security/policy-drift\";\nimport { describeDriverError, type Queryable } from \"./ensure-collection-tables\";\nimport { assertSafeIdentifier } from \"./plan/diff-plan\";\nimport { quoteSqlLiteral } from \"./generate-postgres-ddl-logic\";\nimport { revokeUserRoleSql } from \"../security/rls-enforcement\";\nimport {\n atomicBatch,\n isLockTimeout,\n isMultiStatementRefusal,\n resolveBootLockOptions,\n sleep,\n type BootLockOptions,\n type ResolvedBootLockOptions\n} from \"./boot-ddl\";\n\nexport interface PolicyEnsureResult {\n /** `CREATE POLICY` statements that ran successfully. */\n policiesApplied: number;\n /** Tables that had RLS enabled — including the {@link unchanged} ones, which already had it. */\n tablesSecured: number;\n /**\n * Tables whose policies were already exactly the declared ones, RLS on, by\n * the stamp each policy carries — left alone: no statement, no lock.\n */\n unchanged: number;\n /** Declared tables absent from the database — left to a real migration. */\n skipped: { table: string; reason: string }[];\n /**\n * Tables that have RLS on but did not get every policy. They deny — RLS\n * with no matching policy is deny-all — so they are safe but not servable.\n */\n failures: { table: string; error: string }[];\n /**\n * Tables RLS could not be enabled on, whose DML grant was withdrawn instead.\n *\n * This state had no name, and that was the bug: `ENABLE ROW LEVEL SECURITY`\n * failing was recorded as a `failure` and reported with the same \"it stays\n * locked (denies)\" wording as a failed policy — but the two are opposites.\n * A policy statement failing leaves RLS on and the table denying. `enableRls`\n * failing leaves RLS *off*, and the schema-wide grant to the user role has\n * already been made by `ensureRlsEnforcement`, so the table is readable and\n * writable by every authenticated request with no row filtering at all.\n */\n unsecured: { table: string; error: string; grantWithdrawn: boolean }[];\n /**\n * Generated policies removed because no current rule produces them.\n *\n * A policy's name embeds a hash of the rule's semantics, so editing a rule\n * does not update a policy — it creates a new one and abandons the old.\n * Postgres ORs permissive policies, so the abandoned one keeps granting:\n * a `USING (true)` tightened to an owner check went on admitting everyone,\n * forever, while the deploy logged success.\n *\n * `db push` reconciles this, and cannot reach a managed tenant's in-cluster\n * database — which is the reason this module exists. So boot has to do it.\n */\n orphansDropped: number;\n /**\n * Tables whose policies were not re-applied because another session held\n * the table's lock for every attempt — a long transaction, a report, a\n * dump. Each table's policy statements run in one transaction, so a table\n * here was left exactly as it was: RLS on, its existing policies in force.\n * `rulesChanged` says whether those differ from the declared ones by name\n * (a rule whose policy is missing, or an orphan still granting), in which\n * case the new rules are not in force on this table until a later boot.\n */\n deferred: { table: string; error: string; rulesChanged: boolean }[];\n /**\n * Tables whose collection declares no rule of its own and which still held\n * generated rule policies, all of them dropped: the generated policies, by\n * name, the boot is to warn about.\n *\n * An edited rule supersedes its old policy, and that drop is the point of\n * {@link PolicyEnsureResult.orphansDropped}. A collection with no rule at\n * all is something else: what a collection looks like when the\n * `defaultSecurityRules` its directory's index declares never reached it —\n * an array handed to a backend without them. Dropping is still right (the\n * declared state wins, and the table is left admin-only rather than open),\n * but it changes who can read the table on every server sharing the\n * database, so it is not done in silence.\n */\n rulesDropped: { table: string; policies: string[] }[];\n}\n\nconst isCreatePolicy = (statement: string): boolean => /^\\s*CREATE POLICY/i.test(statement);\n\n/** The policy names a plan creates. */\nconst plannedPolicyNames = (plan: CollectionPolicyPlan): Set<string> => {\n const expected = new Set<string>();\n for (const statement of plan.policyStatements) {\n const match = /^\\s*CREATE POLICY\\s+\"([^\"]+)\"/i.exec(statement);\n if (match) expected.add(match[1]);\n }\n return expected;\n};\n\n/**\n * Generated policies on this table that the current plan does not produce.\n *\n * Scoped hard, because dropping a policy is destructive: only this one table,\n * only names matching the generated `<table>_<op>_<sha1>` shape, and only names\n * absent from the plan. A hand-written policy, or one belonging to another\n * table, is never touched — `isGeneratedPolicyName` is the same predicate `db\n * push` uses to draw that line.\n *\n * Computed from the names read before anything is applied. The plan only ever\n * drops and re-creates its own names, so \"present before, not planned\" is the\n * same set the old read-after-apply found.\n */\nconst orphansOf = (plan: CollectionPolicyPlan, present: readonly string[]): string[] => {\n const expected = plannedPolicyNames(plan);\n return present.filter(name => !expected.has(name) && isGeneratedPolicyName(name, plan.table));\n};\n\nconst dropPolicySql = (plan: CollectionPolicyPlan, name: string): string =>\n `DROP POLICY IF EXISTS \"${name}\" ON \"${plan.schema}\".\"${plan.table}\"`;\n\n/** What every policy stamp starts with. See {@link policyStampSql}. */\nexport const POLICY_STAMP_PREFIX = \"rebase:\";\n\nconst sha256Hex = (text: string): string => createHash(\"sha256\").update(text, \"utf8\").digest(\"hex\");\n\n/** The same digest, computed by Postgres: hex SHA-256 of the text's UTF-8 bytes. */\nconst sha256Sql = (expression: string): string => `encode(sha256(convert_to(${expression}, 'UTF8')), 'hex')`;\n\n/**\n * The definition Postgres holds for the `pg_policy` row aliased `p`, as one\n * text: command, permissive/restrictive, the role OIDs, and the deparsed\n * `USING` and `WITH CHECK` (`-` for an absent clause, so absent and empty\n * differ). Everything `ALTER POLICY` can change, and everything a `CREATE\n * POLICY` sets.\n *\n * Read back with `pg_get_expr`, whose spelling depends on `search_path` (a\n * name on the path prints unqualified). The stamp is written and checked by\n * the same boot connections, so the two agree; a process with a different\n * path would only disagree, and re-apply — what every boot did before stamps.\n */\nconst liveDefinitionSql = (p: string): string =>\n `concat_ws(E'\\\\n', ${p}.polcmd::text, ${p}.polpermissive::text, ${p}.polroles::text, ` +\n `coalesce(pg_get_expr(${p}.polqual, ${p}.polrelid), '-'), ` +\n `coalesce(pg_get_expr(${p}.polwithcheck, ${p}.polrelid), '-'))`;\n\n/** The stamp a policy rendered as `renderedHash` carries when its stored definition hashes to `liveHash`. */\nconst stampOf = (renderedHash: string, liveHash: string): string =>\n `${POLICY_STAMP_PREFIX}${sha256Hex(`${renderedHash}:${liveHash}`)}`;\n\n/** The policies a plan creates, by name, each with the hash of its `CREATE POLICY` statement. */\nconst renderedHashes = (plan: CollectionPolicyPlan): Map<string, string> => {\n const hashes = new Map<string, string>();\n for (const statement of plan.policyStatements) {\n const match = /^\\s*CREATE POLICY\\s+\"([^\"]+)\"/i.exec(statement);\n if (match) hashes.set(match[1], sha256Hex(statement.trim()));\n }\n return hashes;\n};\n\n/**\n * One statement that stamps every policy the plan just created on its table:\n * `COMMENT ON POLICY … IS 'rebase:<hash>'`, the hash taken over the rendered\n * statement's hash and the definition Postgres stored — read back here, after\n * the `CREATE`s, in the same transaction. A `DO` block because the stored\n * definition is only known once the policy exists, and a `COMMENT` takes a\n * literal. One line, and it names only this table.\n *\n * `undefined` for a table the plan gives no policies.\n */\nexport function policyStampSql(plan: CollectionPolicyPlan): string | undefined {\n const hashes = renderedHashes(plan);\n if (hashes.size === 0) return undefined;\n const target = `\"${plan.schema}\".\"${plan.table}\"`;\n const values = [...hashes].map(([name, hash]) => `(${quoteSqlLiteral(name)}, '${hash}')`).join(\", \");\n const stamp = `${quoteSqlLiteral(POLICY_STAMP_PREFIX)} || ${sha256Sql(`s.rendered || ':' || ${sha256Sql(liveDefinitionSql(\"p\"))}`)}`;\n return \"DO $rebase_policy_stamp$ DECLARE r record; BEGIN \" +\n `FOR r IN SELECT p.polname AS name, ${stamp} AS stamp FROM pg_policy p ` +\n `JOIN (VALUES ${values}) AS s(name, rendered) ON s.name = p.polname ` +\n `WHERE p.polrelid = ${quoteSqlLiteral(target)}::regclass LOOP ` +\n `EXECUTE 'COMMENT ON POLICY ' || quote_ident(r.name) || ${quoteSqlLiteral(` ON ${target} IS `)} || quote_literal(r.stamp); ` +\n \"END LOOP; END $rebase_policy_stamp$;\";\n}\n\n/** One policy as the catalogue has it. */\ninterface LivePolicy {\n /** Its comment — a stamp, when this module wrote it. */\n stamp: string | null;\n /** Hex SHA-256 of {@link liveDefinitionSql}. */\n live: string;\n}\n\n/**\n * Every policy on every table in the declared schemas — name, stamp and a hash\n * of its stored definition — in one read. It replaced a `pg_policies` query\n * per table, and later the name-only read that preceded stamps.\n */\nasync function readLivePolicies(client: Queryable, schemas: string[]): Promise<Map<string, Map<string, LivePolicy>>> {\n const policies = new Map<string, Map<string, LivePolicy>>();\n if (schemas.length === 0) return policies;\n const inList = schemas.map(schema => `'${assertSafeIdentifier(schema, \"schema name\")}'`).join(\", \");\n const { rows } = await client.query<{\n schemaname: string; tablename: string; policyname: string; stamp?: string | null; live?: string | null;\n }>(\n `SELECT n.nspname AS schemaname, c.relname AS tablename, p.polname AS policyname,\n obj_description(p.oid, 'pg_policy') AS stamp, ${sha256Sql(liveDefinitionSql(\"p\"))} AS live\n FROM pg_policy p\n JOIN pg_class c ON c.oid = p.polrelid\n JOIN pg_namespace n ON n.oid = c.relnamespace\n WHERE n.nspname IN (${inList})`\n );\n for (const row of rows) {\n const key = `${row.schemaname}.${row.tablename}`;\n if (!policies.has(key)) policies.set(key, new Map());\n policies.get(key)!.set(row.policyname, { stamp: row.stamp ?? null, live: row.live ?? \"\" });\n }\n return policies;\n}\n\n/**\n * Whether every policy the plan declares is on the table, stamped for exactly\n * the statement the plan would run and exactly the definition it holds now.\n */\nconst policiesAreCurrent = (plan: CollectionPolicyPlan, present: Map<string, LivePolicy>): boolean => {\n for (const [name, renderedHash] of renderedHashes(plan)) {\n const policy = present.get(name);\n if (!policy || policy.stamp !== stampOf(renderedHash, policy.live)) return false;\n }\n return true;\n};\n\n/**\n * What its stamp says of a declared policy the database has: written by Rebase\n * from the statement the plan renders now and still holding that definition\n * (`current`), stamped once but changed since — the rule's SQL, or the policy\n * by hand (`stale`), or carrying no stamp at all, so nothing tells whether a\n * re-apply changes it (`unstamped`).\n */\nexport type PolicyStampState = \"current\" | \"stale\" | \"unstamped\";\n\n/**\n * Every declared policy the database holds, by `schema.table`, then name, with\n * its {@link PolicyStampState}. The comparison a boot makes to leave a table\n * alone, read for each policy instead of each table.\n *\n * The stored definition is read with `pg_get_expr`, whose spelling depends on\n * `search_path`: call it under the path the stamps were written under (a\n * boot's, `public` unless the URL pins one), or a current stamp reads stale.\n */\nexport async function readPolicyStamps(\n client: Queryable,\n collections: CollectionConfig[]\n): Promise<Map<string, Map<string, PolicyStampState>>> {\n const plans = planCollectionPolicies(collections);\n const live = await readLivePolicies(client, Array.from(new Set(plans.map(p => p.schema))));\n const states = new Map<string, Map<string, PolicyStampState>>();\n for (const plan of plans) {\n const present = live.get(plan.qualified);\n if (!present) continue;\n const table = new Map<string, PolicyStampState>();\n for (const [name, renderedHash] of renderedHashes(plan)) {\n const policy = present.get(name);\n if (!policy) continue;\n table.set(name, policy.stamp === null || !policy.stamp.startsWith(POLICY_STAMP_PREFIX)\n ? \"unstamped\"\n : policy.stamp === stampOf(renderedHash, policy.live) ? \"current\" : \"stale\");\n }\n states.set(plan.qualified, table);\n }\n return states;\n}\n\n/**\n * Every declared schema's tables, and whether each has row-level security on.\n *\n * One catalogue read for the whole step. It replaced a full `readExistingSchema`\n * — eight catalogue queries and a row probe of every table, of which only the\n * table names were used — and it carries the one fact that lets a boot leave an\n * already-locked table alone: `relrowsecurity`.\n *\n * Presence is still decided by `information_schema.columns`, as it was: the\n * same relations (views included, which have no RLS and are closed by revoking,\n * exactly as before) and the same privilege filter.\n */\nasync function readTableSecurity(client: Queryable, schemas: string[]): Promise<Map<string, { rls: boolean }>> {\n const tables = new Map<string, { rls: boolean }>();\n if (schemas.length === 0) return tables;\n const inList = schemas.map(schema => `'${assertSafeIdentifier(schema, \"schema name\")}'`).join(\", \");\n const { rows } = await client.query<{ table_schema: string; table_name: string; rls: boolean | null }>(\n `SELECT t.table_schema, t.table_name, c.relrowsecurity AS rls\n FROM (SELECT DISTINCT table_schema, table_name FROM information_schema.columns\n WHERE table_schema IN (${inList})) t\n JOIN pg_namespace n ON n.nspname = t.table_schema\n JOIN pg_class c ON c.relnamespace = n.oid AND c.relname = t.table_name`\n );\n for (const row of rows) tables.set(`${row.table_schema}.${row.table_name}`, { rls: row.rls === true });\n return tables;\n}\n\n/**\n * Whether the catalogue says RLS is on for one table, read fresh. `false` when\n * it cannot be read, which sends the caller down the fail-closed path.\n */\nasync function rlsIsOn(client: Queryable, plan: CollectionPolicyPlan): Promise<boolean> {\n try {\n const { rows } = await client.query<{ rls: boolean }>(\n `SELECT c.relrowsecurity AS rls FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace\n WHERE n.nspname = ${quoteSqlLiteral(plan.schema)} AND c.relname = ${quoteSqlLiteral(plan.table)}`\n );\n return rows[0]?.rls === true;\n } catch {\n return false;\n }\n}\n\n/** What one table's `ENABLE ROW LEVEL SECURITY` came to. */\ntype TableLock =\n | { locked: true }\n | { locked: false; error: string; grantWithdrawn: boolean };\n\n/**\n * Switch RLS on for one table, or — when Postgres refuses — take the user\n * role's privileges on it back, so the table is unreachable rather than\n * unprotected.\n *\n * A table the catalogue already reports RLS-on is left alone. `ENABLE` on it\n * changes nothing, and it is not free: it queues for an `ACCESS EXCLUSIVE` lock,\n * so a boot that met a long reader held every live query on that table behind\n * it — and when the wait ended in a timeout, the catch below revoked the user\n * role's privileges on a table that had been protected all along. Every\n * replica shares the grants, so one boot during a `pg_dump` closed the\n * collection for the whole deployment until the next clean boot.\n */\nasync function lockTable(\n client: Queryable,\n plan: CollectionPolicyPlan,\n rlsOn: boolean,\n run: (sql: string) => Promise<unknown> = sql => client.query(sql)\n): Promise<TableLock> {\n if (rlsOn) return { locked: true };\n try {\n await run(plan.enableRls);\n return { locked: true };\n } catch (err) {\n // The database's own answer, not the ORM's wrapper — see\n // `describeDriverError`. This is the most consequential message in\n // the file: it is what the operator reads about a table that is now\n // denying every request, and `Failed query: <the statement>` told\n // them nothing they did not already have.\n const error = describeDriverError(err);\n // A failed statement says nothing about the state it meant to reach: a\n // lock or statement timeout, a cancelled query, a peer that enabled it\n // first. Ask the catalogue. Only a table that really is RLS-off is\n // revoked — that safety is unchanged; a read that fails counts as off.\n if (await rlsIsOn(client, plan)) return { locked: true };\n // Take the privilege back rather than serve an unprotected table.\n // This is the fail-closed step the old code assumed it already had:\n // per-table, so one collection cannot take the rest of the\n // deployment down, but leaving nothing readable without RLS.\n //\n // Quoted, like every other statement here: unquoted, Postgres folds\n // the name to lower case, so a mixed-case adopted table (`\"User\"`)\n // was revoked as `public.user` — which fails, and a table that could\n // have been closed refused the boot instead.\n return withdrawGrant(client, plan, error);\n }\n}\n\n/**\n * Take the user role's privileges on a table back, so an RLS-off table is\n * unreachable rather than unprotected. `REVOKE` waits for no table lock a\n * reader holds, so it runs as a plain statement.\n */\nasync function withdrawGrant(client: Queryable, plan: CollectionPolicyPlan, error: string): Promise<TableLock> {\n try {\n await client.query(revokeUserRoleSql(plan.schema, plan.table));\n return { locked: false, error, grantWithdrawn: true };\n } catch {\n // Reported with `grantWithdrawn: false`, which the caller escalates.\n // There is nothing else this can do to make the table safe.\n return { locked: false, error, grantWithdrawn: false };\n }\n}\n\nexport interface TableLockResult {\n /** Tables RLS is now on for. */\n locked: number;\n /** Tables it could not be switched on for. See {@link PolicyEnsureResult.unsecured}. */\n unsecured: PolicyEnsureResult[\"unsecured\"];\n}\n\n/**\n * Switch RLS on for every table the collections declare — each collection's\n * and each junction's — and do nothing else.\n *\n * The first thing `rebase db push` does once Atlas has committed. Atlas cannot\n * carry `ENABLE ROW LEVEL SECURITY` (its free tier neither plans nor reads it),\n * so a table it creates arrives with RLS off — while the user role already\n * holds DML on it through the schema's default privileges. Everything push runs\n * after Atlas can refuse: the vector, search and trigger DDL, the policies\n * themselves. Locking first means none of those failures can leave a table\n * open: RLS with no policy denies, which is a state a failed push may leave.\n *\n * A declared table that is not in the database is skipped — nothing can reach\n * a table that does not exist, and the step that creates it locks it.\n */\nexport async function lockCollectionTables(\n client: Queryable,\n collections: CollectionConfig[]\n): Promise<TableLockResult> {\n const result: TableLockResult = { locked: 0, unsecured: [] };\n for (const { plan, rls } of await declaredTablesPresent(client, collections)) {\n const lock = await lockTable(client, plan, rls);\n if (lock.locked) {\n result.locked++;\n } else {\n result.unsecured.push({ table: plan.qualified, error: lock.error, grantWithdrawn: lock.grantWithdrawn });\n }\n }\n return result;\n}\n\n/**\n * The declared tables {@link lockCollectionTables} would switch row-level\n * security on for: those in the database with it off. What `db push\n * --dry-run` lists, read the way the push's lock step reads it.\n */\nexport async function tablesRlsWouldEnable(client: Queryable, collections: CollectionConfig[]): Promise<string[]> {\n return (await declaredTablesPresent(client, collections)).filter(table => !table.rls).map(table => table.plan.qualified);\n}\n\n/** Each declared table the database has, with whether its RLS is on. */\nasync function declaredTablesPresent(\n client: Queryable,\n collections: CollectionConfig[]\n): Promise<{ plan: CollectionPolicyPlan; rls: boolean }[]> {\n const plans = planCollectionPolicies(collections);\n if (plans.length === 0) return [];\n const tables = await readTableSecurity(client, Array.from(new Set(plans.map(p => p.schema))));\n return plans.flatMap(plan => {\n const table = tables.get(plan.qualified);\n return table ? [{ plan, rls: table.rls }] : [];\n });\n}\n\n/** One declared table that exists, and what the catalogue said about it. */\ninterface TableWork {\n plan: CollectionPolicyPlan;\n rlsOn: boolean;\n orphans: string[];\n /** Whether the policy names on the table differ from the plan's. */\n rulesChanged: boolean;\n /** Whether the plan holds no generated rule of its own, so the orphans are every rule the table had. */\n declaresNoRule: boolean;\n}\n\ntype AtomicOutcome =\n | { kind: \"applied\" }\n | { kind: \"lock-timeout\"; error: string }\n | { kind: \"unsupported\" }\n | { kind: \"failed\"; error: string };\n\n/**\n * Bring the declared collections' RLS policies up to date. Returns what it did.\n *\n * Only tables that already exist are touched: the boot-time table creator runs\n * first, so anything still missing is a table this additive path did not\n * create (it creates junctions too) — one a failed boot DDL left out, or a\n * project whose schema a migration owns. Enabling RLS on a\n * non-existent table would error, so those are recorded as skipped, not failed.\n *\n * ## One transaction per table\n *\n * Each table's statements — `ENABLE` when RLS is off, every `DROP POLICY IF\n * EXISTS` / `CREATE POLICY` pair, the orphan drops — go to Postgres as one\n * string, which it runs as one transaction (see `boot-ddl`). Before, each was\n * its own autocommit: at 240 tables, 4,080 commits and 4,080 ACCESS EXCLUSIVE\n * lock acquisitions per boot, 6–22 s on a disk that really flushes. And between\n * a policy's DROP and its CREATE, every other replica evaluated the table with\n * that policy missing — a user's continuous reads of their own row came back\n * empty once per boot (measured, Postgres 18). Now a concurrent request sees the\n * old policies or the new ones, never neither.\n *\n * If the transaction fails for any reason but a lock timeout, the table is\n * applied again one statement at a time, exactly as before — so a rule that\n * cannot be created (a function a migration has not added yet) leaves the\n * table in the same deny-leaning partial state it always did, rather than\n * rolling back to the old, possibly looser, policies.\n *\n * ## Lock budget\n *\n * The transaction runs under `lock_timeout` (`boot-ddl`), so a table that a\n * long transaction, a report or a dump is holding costs live traffic one\n * second, not the statement timeout. A contended table is retried after the\n * others, with backoff, within a retry budget for the whole step; one still\n * held after that is reported in `deferred` — its transaction rolled back, so\n * it is exactly as it was before this boot — or, if RLS was off on it, closed\n * by revoking the user role's grant, as any table RLS cannot be enabled on is.\n */\nexport async function ensureCollectionPolicies(\n client: Queryable,\n collections: CollectionConfig[],\n log?: (message: string) => void,\n options: BootLockOptions = {}\n): Promise<PolicyEnsureResult> {\n const result: PolicyEnsureResult = {\n policiesApplied: 0, tablesSecured: 0, unchanged: 0, skipped: [], failures: [], unsecured: [],\n orphansDropped: 0, deferred: [], rulesDropped: []\n };\n\n const plans = planCollectionPolicies(collections);\n if (plans.length === 0) return result;\n const lock = resolveBootLockOptions(options);\n\n const schemas = Array.from(new Set(plans.map(p => p.schema)));\n const tables = await readTableSecurity(client, schemas);\n const livePolicies = await readLivePolicies(client, schemas);\n\n const work: TableWork[] = [];\n for (const plan of plans) {\n const table = tables.get(plan.qualified);\n if (!table) {\n result.skipped.push({\n table: plan.qualified,\n reason: \"table is not present in the database; create it with `rebase db push` / `rebase db migrate`\"\n });\n continue;\n }\n const live = livePolicies.get(plan.qualified) ?? new Map<string, LivePolicy>();\n const present = [...live.keys()];\n const orphans = orphansOf(plan, present);\n // Already exactly what the plan would leave: nothing to run, no lock to take.\n if (table.rls && orphans.length === 0 && policiesAreCurrent(plan, live)) {\n result.tablesSecured++;\n result.unchanged++;\n continue;\n }\n const presentSet = new Set(present);\n const rulesChanged = orphans.length > 0 || [...plannedPolicyNames(plan)].some(name => !presentSet.has(name));\n const declaresNoRule = ![...plannedPolicyNames(plan)].some(name => isGeneratedPolicyName(name, plan.table));\n work.push({ plan, rlsOn: table.rls, orphans, rulesChanged, declaresNoRule });\n }\n if (result.unchanged > 0) {\n log?.(`${result.unchanged} table(s) already have exactly the declared policies; left as they are`);\n }\n\n // Until the handle refuses a multi-statement string (an extended-protocol\n // driver), after which every table goes one statement at a time.\n let atomic = true;\n let retryBudget = lock.retryBudgetMs;\n let queue = work;\n let lastError = new Map<TableWork, string>();\n\n for (let attempt = 1; queue.length > 0; attempt++) {\n if (attempt > 1) {\n if (attempt > lock.attempts || retryBudget <= 0) break;\n await sleep(lock.backoffMs(attempt - 1));\n }\n const contended: TableWork[] = [];\n const errors = new Map<TableWork, string>();\n for (const table of queue) {\n if (attempt > 1 && retryBudget <= 0) {\n contended.push(table);\n errors.set(table, lastError.get(table) ?? \"\");\n continue;\n }\n if (atomic) {\n const outcome = await applyAtomically(client, table, lock);\n if (outcome.kind === \"applied\") {\n record(result, table, log);\n continue;\n }\n if (outcome.kind === \"lock-timeout\") {\n if (attempt > 1) retryBudget -= lock.lockTimeoutMs;\n contended.push(table);\n errors.set(table, outcome.error);\n continue;\n }\n if (outcome.kind === \"unsupported\") {\n atomic = false;\n } else {\n log?.(`${table.plan.qualified}: applying its policies in one transaction failed ` +\n `(${outcome.error}); applying them one statement at a time`);\n }\n }\n await applyEachStatement(client, table, result, log, atomic ? lock : undefined);\n }\n queue = contended;\n lastError = errors;\n }\n\n // Still contended after every attempt the budget allowed.\n for (const table of queue) {\n await giveUp(client, table, lastError.get(table) ?? \"\", lock, result);\n }\n\n return result;\n}\n\n/**\n * The table's statements, in the order they must run — the stamp last, so it\n * reads back the policies this same transaction created.\n */\nconst statementsOf = (table: TableWork): string[] => {\n const stamp = policyStampSql(table.plan);\n return [\n ...(table.rlsOn ? [] : [table.plan.enableRls]),\n ...table.plan.policyStatements,\n ...table.orphans.map(name => dropPolicySql(table.plan, name)),\n ...(stamp ? [stamp] : [])\n ];\n};\n\nasync function applyAtomically(\n client: Queryable,\n table: TableWork,\n lock: ResolvedBootLockOptions\n): Promise<AtomicOutcome> {\n const statements = statementsOf(table);\n if (statements.length === 0) return { kind: \"applied\" };\n try {\n await client.query(atomicBatch(statements, lock.lockTimeoutMs));\n return { kind: \"applied\" };\n } catch (err) {\n if (isLockTimeout(err)) return { kind: \"lock-timeout\", error: describeDriverError(err) };\n if (isMultiStatementRefusal(err)) return { kind: \"unsupported\" };\n return { kind: \"failed\", error: describeDriverError(err) };\n }\n}\n\n/**\n * The warning a boot gives for one entry of {@link PolicyEnsureResult.rulesDropped}:\n * each policy, what it means for the table, and the likely cause.\n */\nexport function describeRulesDropped(dropped: { table: string; policies: string[] }): string {\n return `🔐 [rls] \"${dropped.table}\" declares no securityRules, so the policies its rules had made were dropped: ` +\n `${dropped.policies.join(\", \")}. Only administrators reach its rows now, on every server sharing this database. ` +\n \"If its collection came in an array handed to createRebaseBackend, that array lacked the defaultSecurityRules \" +\n \"its config/collections/index.ts declares: pass collectionsDir, or the array that index exports, which carries \" +\n \"them. If the rules were removed on purpose, this is that change.\";\n}\n\n/** The orphans just dropped, when they were every rule the table had. See {@link PolicyEnsureResult.rulesDropped}. */\nfunction recordRulesDropped(result: PolicyEnsureResult, table: TableWork): void {\n if (table.declaresNoRule && table.orphans.length > 0) {\n result.rulesDropped.push({ table: table.plan.qualified, policies: [...table.orphans].sort() });\n }\n}\n\nfunction record(result: PolicyEnsureResult, table: TableWork, log?: (message: string) => void): void {\n const created = table.plan.policyStatements.filter(isCreatePolicy).length;\n result.tablesSecured++;\n result.policiesApplied += created;\n result.orphansDropped += table.orphans.length;\n recordRulesDropped(result, table);\n log?.(`${table.plan.qualified}: RLS enabled, ${created} policy(ies) applied` +\n (table.orphans.length > 0 ? `, ${table.orphans.length} orphan(s) dropped` : \"\"));\n}\n\n/**\n * The table applied one statement at a time — the path every table took before\n * the per-table transaction, kept as its fallback so a failure ends exactly\n * where it always did.\n *\n * Each statement still carries the lock budget when the handle can take it.\n */\nasync function applyEachStatement(\n client: Queryable,\n table: TableWork,\n result: PolicyEnsureResult,\n log: ((message: string) => void) | undefined,\n lock: ResolvedBootLockOptions | undefined\n): Promise<void> {\n const { plan } = table;\n const run = (sql: string) => client.query(lock ? atomicBatch([sql], lock.lockTimeoutMs) : sql);\n\n // Enabling RLS is its own step, because its failure is the opposite of\n // every other failure here. Once RLS is on, anything that goes wrong\n // afterwards leaves the table denying; while it is off, the grant made\n // earlier in boot leaves the table wide open. Sharing one `try` meant\n // the dangerous case was reported in the safe case's words.\n const locked = await lockTable(client, plan, table.rlsOn, run);\n if (!locked.locked) {\n result.unsecured.push({ table: plan.qualified, error: locked.error, grantWithdrawn: locked.grantWithdrawn });\n return;\n }\n result.tablesSecured++;\n\n try {\n let created = 0;\n for (const statement of plan.policyStatements) {\n await run(statement);\n if (isCreatePolicy(statement)) {\n result.policiesApplied++;\n created++;\n }\n }\n for (const name of table.orphans) {\n await run(dropPolicySql(plan, name));\n result.orphansDropped++;\n }\n recordRulesDropped(result, table);\n log?.(`${plan.qualified}: RLS enabled, ${created} policy(ies) applied` +\n (table.orphans.length > 0 ? `, ${table.orphans.length} orphan(s) dropped` : \"\"));\n } catch (err) {\n result.failures.push({\n table: plan.qualified,\n error: describeDriverError(err)\n });\n return;\n }\n\n // Only once every policy is in. A stamp that cannot be written costs the\n // next boot a re-apply of this table — what every boot did before stamps —\n // so it is reported, not failed.\n const stamp = policyStampSql(plan);\n if (!stamp) return;\n try {\n await run(stamp);\n } catch (err) {\n log?.(`${plan.qualified}: policies applied, but not stamped (${describeDriverError(err)}); ` +\n \"the next boot applies them again\");\n }\n}\n\n/**\n * A table whose lock was held for every attempt. Its transaction rolled back\n * each time, so it is exactly as it was before this boot.\n *\n * With RLS on, that is a safe state to leave it in — reported, not failed. With\n * RLS off it is an open table over the schema-wide grant, so it is closed the\n * way any table RLS cannot be enabled on is: the catalogue is asked once more,\n * and only a table that really is RLS-off has the user role's grant withdrawn.\n */\nasync function giveUp(\n client: Queryable,\n table: TableWork,\n lastError: string,\n lock: ResolvedBootLockOptions,\n result: PolicyEnsureResult\n): Promise<void> {\n const { plan } = table;\n const error =\n `another session held the table's lock for every attempt (lock_timeout ${lock.lockTimeoutMs} ms` +\n `${lastError ? `: ${lastError.split(\"\\n\")[0]}` : \"\"}) — a long transaction, a report or a dump`;\n if (table.rlsOn || await rlsIsOn(client, plan)) {\n result.tablesSecured++;\n result.deferred.push({ table: plan.qualified, error, rulesChanged: table.rulesChanged });\n return;\n }\n const closed = await withdrawGrant(client, plan, error);\n if (!closed.locked) {\n result.unsecured.push({ table: plan.qualified, error: closed.error, grantWithdrawn: closed.grantWithdrawn });\n }\n}\n"],"mappings":"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"}
|