@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,18 +1,20 @@
|
|
|
1
1
|
import { createRequire as __createRequire } from "module";
|
|
2
2
|
__createRequire(import.meta.url);
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { i as outError, r as out, t as chalk } from "./source-
|
|
6
|
-
import { n as planSchema, t as columnPgType } from "./plan-schema-
|
|
7
|
-
import { t as generateSchema } from "./generate-drizzle-schema-logic-
|
|
8
|
-
import { r as renderPgType } from "./render-ddl-
|
|
9
|
-
import {
|
|
3
|
+
import { i as __exportAll } from "./cli-prompt-DXG0yk-h.js";
|
|
4
|
+
import { a as diagnoseDbError, r as deepestErrorMessage, u as parseHostInfo } from "./cli-errors-DiA0XFIS.js";
|
|
5
|
+
import { i as outError, r as out, t as chalk } from "./source-fZb92pN8.js";
|
|
6
|
+
import { n as planSchema, t as columnPgType } from "./plan-schema-Czf3q4-Q.js";
|
|
7
|
+
import { t as generateSchema } from "./generate-drizzle-schema-logic-CPzCRD-8.js";
|
|
8
|
+
import { r as renderPgType } from "./render-ddl-4v88y-Gv.js";
|
|
9
|
+
import { r as readGeneratedSchemaDrift, t as describeSchemaDifference } from "./generated-schema-diff-eZCklRZQ.js";
|
|
10
|
+
import { n as describeDeclarationDifferences, t as compareGeneratedDeclarations } from "./generated-schema-staleness-BvNF3i-s.js";
|
|
10
11
|
import { loadCollectionsFromDirectory } from "@rebasepro/server";
|
|
11
|
-
import { computeSchemaVersion, getDeclaredSubcollections, isManyToMany, isPostgresCollectionConfig } from "@rebasepro/types";
|
|
12
|
+
import { DEFAULT_DATA_SOURCE_KEY, computeSchemaVersion, getDeclaredSubcollections, isManyToMany, isPostgresCollectionConfig } from "@rebasepro/types";
|
|
12
13
|
import { effectiveAccess, fieldKeyForColumn, findRelation, getTableName, isRelationRequired, relationalCollections, resolveCollectionRelations, resolveJunctionSpecs, sortCollectionsBySlug, toCollectionAccessor, toSafeIdentifier } from "@rebasepro/common";
|
|
13
14
|
import { toPostgresIdentifier, toSnakeCase } from "@rebasepro/utils";
|
|
14
15
|
import * as fs$1 from "fs";
|
|
15
16
|
import { promises } from "fs";
|
|
17
|
+
import { pathToFileURL } from "url";
|
|
16
18
|
//#region ../codegen/dist/index.es.js
|
|
17
19
|
/**
|
|
18
20
|
* A schema that cannot be expressed as a valid TypeScript file.
|
|
@@ -545,6 +547,7 @@ var doctor_exports = /* @__PURE__ */ __exportAll({
|
|
|
545
547
|
checkCollectionsVsDatabase: () => checkCollectionsVsDatabase,
|
|
546
548
|
checkCollectionsVsSchema: () => checkCollectionsVsSchema,
|
|
547
549
|
checkCollectionsVsSdk: () => checkCollectionsVsSdk,
|
|
550
|
+
generatedSchemaDriftIssues: () => generatedSchemaDriftIssues,
|
|
548
551
|
getExpectedColumnType: () => getExpectedColumnType,
|
|
549
552
|
junctionKeyIssues: () => junctionKeyIssues,
|
|
550
553
|
loadCollections: () => loadCollections,
|
|
@@ -860,7 +863,63 @@ function junctionKeyIssues(junctionPlan, dbColumns, dbPk, displayName) {
|
|
|
860
863
|
issues.push(...primaryKeyIssues(junctionPlan, dbPk, displayName));
|
|
861
864
|
return issues;
|
|
862
865
|
}
|
|
863
|
-
|
|
866
|
+
/** A table as the other issues name it: bare in `public`, `schema.table` elsewhere. */
|
|
867
|
+
function displayTableOf(difference, collections) {
|
|
868
|
+
const collection = difference.collection ? collections.find((c) => c.slug === difference.collection) : void 0;
|
|
869
|
+
const schema = collection && isPostgresCollectionConfig(collection) ? collection.schema : void 0;
|
|
870
|
+
return schema && schema !== "public" ? `${schema}.${difference.table}` : difference.table;
|
|
871
|
+
}
|
|
872
|
+
/** Which of the doctor's own categories already says what a boot difference says. */
|
|
873
|
+
var COVERING_CATEGORY = {
|
|
874
|
+
"missing-table": "missing_table",
|
|
875
|
+
"missing-column": "missing_column",
|
|
876
|
+
type: "type_mismatch",
|
|
877
|
+
nullability: "nullability_mismatch",
|
|
878
|
+
"extra-column": "extra_column"
|
|
879
|
+
};
|
|
880
|
+
/** What to do about one boot difference, said the way the boot warning says it. */
|
|
881
|
+
function generatedDriftFix(difference) {
|
|
882
|
+
switch (difference.kind) {
|
|
883
|
+
case "nullability": return difference.generated === "NOT NULL" ? "`rebase db push` applies it; a NOT NULL needs every row filled first (UPDATE the empty ones, or give the property a default). Boot adds one only with a new column on an empty table. If the collections changed since schema.generated.ts was written, run `rebase schema generate` first." : "`rebase db push` drops the NOT NULL, which boot never does. If the column should stay required, mark the property `validation: { required: true }`. If the collections changed since schema.generated.ts was written, run `rebase schema generate` first.";
|
|
884
|
+
case "extra-column": return "Boot never drops a column, so its data is still there. `rebase db push` removes it (it asks first, since that deletes the data), or a migration does (`rebase db generate`). If a property should still read it, declare it again. If the collections changed since schema.generated.ts was written, run `rebase schema generate` first.";
|
|
885
|
+
case "type": return "`rebase db push` converts it (it asks first when values may not convert), or a migration does. If the collections changed since schema.generated.ts was written, run `rebase schema generate` first.";
|
|
886
|
+
case "missing-column": return "`rebase db push` adds it. If the collections changed since schema.generated.ts was written, run `rebase schema generate` first.";
|
|
887
|
+
case "missing-table": return "Run `rebase db push` or `rebase db generate && rebase db migrate`";
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* Boot's drift warnings, as doctor issues — one per difference, in boot's own
|
|
892
|
+
* words ({@link describeSchemaDifference}), and a warning, as boot raises them.
|
|
893
|
+
*
|
|
894
|
+
* Except where an issue the collections-side checks above already raised says
|
|
895
|
+
* the same thing: a missing table, a missing column or a type mismatch on the
|
|
896
|
+
* same column. Nullability and columns no collection declares are boot's alone;
|
|
897
|
+
* the doctor never compared them, which is how it came to say "In sync" beside
|
|
898
|
+
* a boot that had just warned about both.
|
|
899
|
+
*/
|
|
900
|
+
function generatedSchemaDriftIssues(differences, already, collections) {
|
|
901
|
+
const bare = (table) => table?.split(".").pop();
|
|
902
|
+
const covered = (difference) => already.some((issue) => issue.category === COVERING_CATEGORY[difference.kind] && bare(issue.table) === difference.table && (difference.kind === "missing-table" || issue.column === difference.column));
|
|
903
|
+
return differences.filter((difference) => !covered(difference)).map((difference) => ({
|
|
904
|
+
severity: "warning",
|
|
905
|
+
category: COVERING_CATEGORY[difference.kind],
|
|
906
|
+
table: displayTableOf(difference, collections),
|
|
907
|
+
...difference.column ? { column: difference.column } : {},
|
|
908
|
+
...difference.generated ? { expected: difference.generated } : {},
|
|
909
|
+
...difference.live ? { actual: difference.live } : {},
|
|
910
|
+
message: describeSchemaDifference(difference),
|
|
911
|
+
fix: generatedDriftFix(difference)
|
|
912
|
+
}));
|
|
913
|
+
}
|
|
914
|
+
/**
|
|
915
|
+
* The collections whose tables the default database holds — the filter boot
|
|
916
|
+
* applies before it reads the catalogue, for the same reason: a collection
|
|
917
|
+
* routed to `database("analytics")` has no table here.
|
|
918
|
+
*/
|
|
919
|
+
function defaultSourceCollections(collections) {
|
|
920
|
+
return collections.filter((c) => !c.dataSource || c.dataSource === DEFAULT_DATA_SOURCE_KEY);
|
|
921
|
+
}
|
|
922
|
+
async function checkCollectionsVsDatabase(collections, databaseUrl, options = {}) {
|
|
864
923
|
const issues = [];
|
|
865
924
|
const pgModule = await import("pg");
|
|
866
925
|
const { Pool } = pgModule.default ?? pgModule;
|
|
@@ -1099,6 +1158,15 @@ async function checkCollectionsVsDatabase(collections, databaseUrl) {
|
|
|
1099
1158
|
}
|
|
1100
1159
|
}
|
|
1101
1160
|
if (plan) issues.push(...orphanedJunctionIssues(plan, columnsByTable, pksByTable, fksByTable));
|
|
1161
|
+
if (options.generated) {
|
|
1162
|
+
const differences = await readGeneratedSchemaDrift({
|
|
1163
|
+
client: pool,
|
|
1164
|
+
generated: options.generated,
|
|
1165
|
+
collections: defaultSourceCollections(collections),
|
|
1166
|
+
defaultSchema: "public"
|
|
1167
|
+
});
|
|
1168
|
+
issues.push(...generatedSchemaDriftIssues(differences, issues, collections));
|
|
1169
|
+
}
|
|
1102
1170
|
} finally {
|
|
1103
1171
|
await pool.end();
|
|
1104
1172
|
}
|
|
@@ -1221,6 +1289,8 @@ function formatCategory(cat) {
|
|
|
1221
1289
|
missing_table: "Missing Table",
|
|
1222
1290
|
missing_column: "Missing Column",
|
|
1223
1291
|
type_mismatch: "Type Mismatch",
|
|
1292
|
+
nullability_mismatch: "Nullability Mismatch",
|
|
1293
|
+
extra_column: "Extra Column",
|
|
1224
1294
|
missing_constraint: "Missing Constraint",
|
|
1225
1295
|
primary_key_mismatch: "Primary Key Mismatch",
|
|
1226
1296
|
schema_stale: "Stale Schema",
|
|
@@ -1233,6 +1303,22 @@ function formatCategory(cat) {
|
|
|
1233
1303
|
sdk_ungeneratable: "SDK Cannot Be Generated"
|
|
1234
1304
|
}[cat];
|
|
1235
1305
|
}
|
|
1306
|
+
var isRecord = (value) => typeof value === "object" && value !== null;
|
|
1307
|
+
/**
|
|
1308
|
+
* The `tables` export of `schema.generated.ts`, imported as boot imports it.
|
|
1309
|
+
*
|
|
1310
|
+
* Nothing, and no error, when the file is not there: phase 1 reports that, and
|
|
1311
|
+
* boot then has nothing to compare either.
|
|
1312
|
+
*/
|
|
1313
|
+
async function loadGeneratedTables(schemaPath) {
|
|
1314
|
+
if (!fs$1.existsSync(schemaPath)) return {};
|
|
1315
|
+
try {
|
|
1316
|
+
const module = await import(pathToFileURL(schemaPath).href);
|
|
1317
|
+
return isRecord(module) && isRecord(module.tables) ? { tables: module.tables } : {};
|
|
1318
|
+
} catch (err) {
|
|
1319
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1236
1322
|
async function runDoctor(options) {
|
|
1237
1323
|
out();
|
|
1238
1324
|
out(chalk.bold(" 🩺 Loading collections..."));
|
|
@@ -1249,7 +1335,17 @@ async function runDoctor(options) {
|
|
|
1249
1335
|
if (options.databaseUrl) {
|
|
1250
1336
|
out(chalk.gray(" Checking Collections → Database..."));
|
|
1251
1337
|
try {
|
|
1252
|
-
|
|
1338
|
+
const generated = await loadGeneratedTables(options.schemaPath);
|
|
1339
|
+
schemaToDatabase = await checkCollectionsVsDatabase(collections, options.databaseUrl, { generated: generated.tables });
|
|
1340
|
+
if (generated.error) {
|
|
1341
|
+
schemaToDatabase.issues.push({
|
|
1342
|
+
severity: "warning",
|
|
1343
|
+
category: "schema_stale",
|
|
1344
|
+
message: `schema.generated.ts could not be loaded, so it was not compared against the database as boot compares it: ${generated.error}`,
|
|
1345
|
+
fix: "Run `rebase schema generate`, then `rebase doctor` again"
|
|
1346
|
+
});
|
|
1347
|
+
schemaToDatabase.passed = false;
|
|
1348
|
+
}
|
|
1253
1349
|
} catch (err) {
|
|
1254
1350
|
const banner = diagnoseDbError(err, options.databaseUrl);
|
|
1255
1351
|
if (banner) outError(banner);
|
|
@@ -1297,4 +1393,4 @@ async function runDoctor(options) {
|
|
|
1297
1393
|
//#endregion
|
|
1298
1394
|
export { loadCollections as n, runDoctor as r, doctor_exports as t };
|
|
1299
1395
|
|
|
1300
|
-
//# sourceMappingURL=doctor-
|
|
1396
|
+
//# sourceMappingURL=doctor-BT--1kq3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor-BT--1kq3.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: '${options.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, DEFAULT_DATA_SOURCE_KEY, 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 { describeSchemaDifference, readGeneratedSchemaDrift, type SchemaDifference } from \"./generated-schema-diff\";\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\" | \"nullability_mismatch\" | \"extra_column\" | \"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\n/** A table as the other issues name it: bare in `public`, `schema.table` elsewhere. */\nfunction displayTableOf(difference: SchemaDifference, collections: CollectionConfig[]): string {\n const collection = difference.collection ? collections.find(c => c.slug === difference.collection) : undefined;\n const schema = collection && isPostgresCollectionConfig(collection) ? collection.schema : undefined;\n return schema && schema !== \"public\" ? `${schema}.${difference.table}` : difference.table;\n}\n\n/** Which of the doctor's own categories already says what a boot difference says. */\nconst COVERING_CATEGORY: Record<SchemaDifference[\"kind\"], DoctorIssue[\"category\"]> = {\n \"missing-table\": \"missing_table\",\n \"missing-column\": \"missing_column\",\n type: \"type_mismatch\",\n nullability: \"nullability_mismatch\",\n \"extra-column\": \"extra_column\"\n};\n\n/** What to do about one boot difference, said the way the boot warning says it. */\nfunction generatedDriftFix(difference: SchemaDifference): string {\n const regenerate = \" If the collections changed since schema.generated.ts was written, run `rebase schema generate` first.\";\n switch (difference.kind) {\n case \"nullability\":\n return difference.generated === \"NOT NULL\"\n ? \"`rebase db push` applies it; a NOT NULL needs every row filled first (UPDATE the empty ones, or give \"\n + \"the property a default). Boot adds one only with a new column on an empty table.\" + regenerate\n : \"`rebase db push` drops the NOT NULL, which boot never does. If the column should stay required, \"\n + \"mark the property `validation: { required: true }`.\" + regenerate;\n case \"extra-column\":\n return \"Boot never drops a column, so its data is still there. `rebase db push` removes it (it asks first, \"\n + \"since that deletes the data), or a migration does (`rebase db generate`). If a property should still \"\n + \"read it, declare it again.\" + regenerate;\n case \"type\":\n return \"`rebase db push` converts it (it asks first when values may not convert), or a migration does.\" + regenerate;\n case \"missing-column\":\n return \"`rebase db push` adds it.\" + regenerate;\n case \"missing-table\":\n return \"Run `rebase db push` or `rebase db generate && rebase db migrate`\";\n }\n}\n\n/**\n * Boot's drift warnings, as doctor issues — one per difference, in boot's own\n * words ({@link describeSchemaDifference}), and a warning, as boot raises them.\n *\n * Except where an issue the collections-side checks above already raised says\n * the same thing: a missing table, a missing column or a type mismatch on the\n * same column. Nullability and columns no collection declares are boot's alone;\n * the doctor never compared them, which is how it came to say \"In sync\" beside\n * a boot that had just warned about both.\n */\nexport function generatedSchemaDriftIssues(\n differences: SchemaDifference[],\n already: DoctorIssue[],\n collections: CollectionConfig[]\n): DoctorIssue[] {\n const bare = (table: string | undefined) => table?.split(\".\").pop();\n const covered = (difference: SchemaDifference) => already.some(issue =>\n issue.category === COVERING_CATEGORY[difference.kind]\n && bare(issue.table) === difference.table\n && (difference.kind === \"missing-table\" || issue.column === difference.column));\n\n return differences.filter(difference => !covered(difference)).map(difference => ({\n severity: \"warning\",\n category: COVERING_CATEGORY[difference.kind],\n table: displayTableOf(difference, collections),\n ...(difference.column ? { column: difference.column } : {}),\n ...(difference.generated ? { expected: difference.generated } : {}),\n ...(difference.live ? { actual: difference.live } : {}),\n message: describeSchemaDifference(difference),\n fix: generatedDriftFix(difference)\n }));\n}\n\n/**\n * The collections whose tables the default database holds — the filter boot\n * applies before it reads the catalogue, for the same reason: a collection\n * routed to `database(\"analytics\")` has no table here.\n */\nfunction defaultSourceCollections(collections: CollectionConfig[]): CollectionConfig[] {\n return collections.filter(c => !c.dataSource || c.dataSource === DEFAULT_DATA_SOURCE_KEY);\n}\n\nexport async function checkCollectionsVsDatabase(\n collections: CollectionConfig[],\n databaseUrl: string,\n options: {\n /**\n * The `tables` export of `schema.generated.ts`, for boot's own drift\n * comparison. Without it the doctor compares the collections alone.\n */\n generated?: Record<string, unknown>;\n } = {}\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\n // The question boot asks, through boot's code: `schema.generated.ts`\n // against the catalogue. Last, so the differences the checks above\n // already named are not named twice.\n if (options.generated) {\n const differences = await readGeneratedSchemaDrift({\n client: pool,\n generated: options.generated,\n collections: defaultSourceCollections(collections),\n defaultSchema: \"public\"\n });\n issues.push(...generatedSchemaDriftIssues(differences, issues, collections));\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 nullability_mismatch: \"Nullability Mismatch\",\n extra_column: \"Extra Column\",\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\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null;\n\n/**\n * The `tables` export of `schema.generated.ts`, imported as boot imports it.\n *\n * Nothing, and no error, when the file is not there: phase 1 reports that, and\n * boot then has nothing to compare either.\n */\nasync function loadGeneratedTables(schemaPath: string): Promise<{ tables?: Record<string, unknown>; error?: string }> {\n if (!fs.existsSync(schemaPath)) return {};\n try {\n const module: unknown = await import(pathToFileURL(schemaPath).href);\n return isRecord(module) && isRecord(module.tables) ? { tables: module.tables } : {};\n } catch (err: unknown) {\n return { error: err instanceof Error ? err.message : String(err) };\n }\n}\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 const generated = await loadGeneratedTables(options.schemaPath);\n schemaToDatabase = await checkCollectionsVsDatabase(collections, options.databaseUrl, { generated: generated.tables });\n if (generated.error) {\n // Boot imports this module too, and would stop on it.\n schemaToDatabase.issues.push({\n severity: \"warning\",\n category: \"schema_stale\",\n message: `schema.generated.ts could not be loaded, so it was not compared against the database as boot compares it: ${generated.error}`,\n fix: \"Run `rebase schema generate`, then `rebase doctor` again\"\n });\n schemaToDatabase.passed = false;\n }\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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC7eA,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;;AAGA,SAAS,eAAe,YAA8B,aAAyC;CAC3F,MAAM,aAAa,WAAW,aAAa,YAAY,MAAK,MAAK,EAAE,SAAS,WAAW,UAAU,IAAI,KAAA;CACrG,MAAM,SAAS,cAAc,2BAA2B,UAAU,IAAI,WAAW,SAAS,KAAA;CAC1F,OAAO,UAAU,WAAW,WAAW,GAAG,OAAO,GAAG,WAAW,UAAU,WAAW;AACxF;;AAGA,IAAM,oBAA+E;CACjF,iBAAiB;CACjB,kBAAkB;CAClB,MAAM;CACN,aAAa;CACb,gBAAgB;AACpB;;AAGA,SAAS,kBAAkB,YAAsC;CAE7D,QAAQ,WAAW,MAAnB;EACI,KAAK,eACD,OAAO,WAAW,cAAc,aAC1B,gSAEA;EAEV,KAAK,gBACD,OAAO;EAGX,KAAK,QACD,OAAO;EACX,KAAK,kBACD,OAAO;EACX,KAAK,iBACD,OAAO;CACf;AACJ;;;;;;;;;;;AAYA,SAAgB,2BACZ,aACA,SACA,aACa;CACb,MAAM,QAAQ,UAA8B,OAAO,MAAM,GAAG,CAAC,CAAC,IAAI;CAClE,MAAM,WAAW,eAAiC,QAAQ,MAAK,UAC3D,MAAM,aAAa,kBAAkB,WAAW,SAC7C,KAAK,MAAM,KAAK,MAAM,WAAW,UAChC,WAAW,SAAS,mBAAmB,MAAM,WAAW,WAAW,OAAO;CAElF,OAAO,YAAY,QAAO,eAAc,CAAC,QAAQ,UAAU,CAAC,CAAC,CAAC,KAAI,gBAAe;EAC7E,UAAU;EACV,UAAU,kBAAkB,WAAW;EACvC,OAAO,eAAe,YAAY,WAAW;EAC7C,GAAI,WAAW,SAAS,EAAE,QAAQ,WAAW,OAAO,IAAI,CAAC;EACzD,GAAI,WAAW,YAAY,EAAE,UAAU,WAAW,UAAU,IAAI,CAAC;EACjE,GAAI,WAAW,OAAO,EAAE,QAAQ,WAAW,KAAK,IAAI,CAAC;EACrD,SAAS,yBAAyB,UAAU;EAC5C,KAAK,kBAAkB,UAAU;CACrC,EAAE;AACN;;;;;;AAOA,SAAS,yBAAyB,aAAqD;CACnF,OAAO,YAAY,QAAO,MAAK,CAAC,EAAE,cAAc,EAAE,eAAe,uBAAuB;AAC5F;AAEA,eAAsB,2BAClB,aACA,aACA,UAMI,CAAC,GACe;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;EAMvF,IAAI,QAAQ,WAAW;GACnB,MAAM,cAAc,MAAM,yBAAyB;IAC/C,QAAQ;IACR,WAAW,QAAQ;IACnB,aAAa,yBAAyB,WAAW;IACjD,eAAe;GACnB,CAAC;GACD,OAAO,KAAK,GAAG,2BAA2B,aAAa,QAAQ,WAAW,CAAC;EAC/E;CACJ,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;CAkB1D,OAAO;EAhBH,eAAe;EACf,gBAAgB;EAChB,eAAe;EACf,sBAAsB;EACtB,cAAc;EACd,oBAAoB;EACpB,sBAAsB;EACtB,cAAc;EACd,cAAc;EACd,qBAAqB;EACrB,qBAAqB;EACrB,mBAAmB;EACnB,WAAW;EACX,mBAAmB;EACnB,mBAAmB;CAEhB,EAAO;AAClB;AAIA,IAAM,YAAY,UACd,OAAO,UAAU,YAAY,UAAU;;;;;;;AAQ3C,eAAe,oBAAoB,YAAmF;CAClH,IAAI,CAAC,KAAG,WAAW,UAAU,GAAG,OAAO,CAAC;CACxC,IAAI;EACA,MAAM,SAAkB,MAAM,OAAO,cAAc,UAAU,CAAC,CAAC;EAC/D,OAAO,SAAS,MAAM,KAAK,SAAS,OAAO,MAAM,IAAI,EAAE,QAAQ,OAAO,OAAO,IAAI,CAAC;CACtF,SAAS,KAAc;EACnB,OAAO,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;CACrE;AACJ;AAEA,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,MAAM,YAAY,MAAM,oBAAoB,QAAQ,UAAU;GAC9D,mBAAmB,MAAM,2BAA2B,aAAa,QAAQ,aAAa,EAAE,WAAW,UAAU,OAAO,CAAC;GACrH,IAAI,UAAU,OAAO;IAEjB,iBAAiB,OAAO,KAAK;KACzB,UAAU;KACV,UAAU;KACV,SAAS,6GAA6G,UAAU;KAChI,KAAK;IACT,CAAC;IACD,iBAAiB,SAAS;GAC9B;EACJ,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"}
|
package/dist/{ensure-collection-policies-C1ux3V_z.js → ensure-collection-policies-BnGOURpG.js}
RENAMED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { createRequire as __createRequire } from "module";
|
|
2
2
|
__createRequire(import.meta.url);
|
|
3
|
-
import { s as revokeUserRoleSql } from "./rls-enforcement-
|
|
4
|
-
import { r as quoteSqlLiteral } from "./plan-schema-
|
|
5
|
-
import { s as planCollectionPolicies } from "./generate-postgres-ddl-logic-
|
|
6
|
-
import { a as assertSafeIdentifier } from "./deferred-index-build-
|
|
7
|
-
import { a as isLockTimeout, c as resolveBootLockOptions, l as sleep, o as isMultiStatementRefusal, r as atomicBatch } from "./boot-ddl-
|
|
8
|
-
import { t as describeDriverError } from "./ensure-collection-tables-
|
|
9
|
-
import { i as isGeneratedPolicyName } from "./policy-drift-
|
|
3
|
+
import { s as revokeUserRoleSql } from "./rls-enforcement-DDyNoOSy.js";
|
|
4
|
+
import { r as quoteSqlLiteral } from "./plan-schema-Czf3q4-Q.js";
|
|
5
|
+
import { s as planCollectionPolicies } from "./generate-postgres-ddl-logic-DPl1oH_z.js";
|
|
6
|
+
import { a as assertSafeIdentifier } from "./deferred-index-build-BE0I6sYU.js";
|
|
7
|
+
import { a as isLockTimeout, c as resolveBootLockOptions, l as sleep, o as isMultiStatementRefusal, r as atomicBatch } from "./boot-ddl-DYH95Qeg.js";
|
|
8
|
+
import { t as describeDriverError } from "./ensure-collection-tables-Cet4650m.js";
|
|
9
|
+
import { i as isGeneratedPolicyName } from "./policy-drift-X4A-xAzv.js";
|
|
10
10
|
import { createHash } from "node:crypto";
|
|
11
11
|
//#region src/schema/ensure-collection-policies.ts
|
|
12
12
|
/**
|
|
@@ -136,9 +136,12 @@ var renderedHashes = (plan) => {
|
|
|
136
136
|
* literal. One line, and it names only this table.
|
|
137
137
|
*
|
|
138
138
|
* `undefined` for a table the plan gives no policies.
|
|
139
|
+
*
|
|
140
|
+
* @param only the names to stamp, when not every policy on the table is known
|
|
141
|
+
* to be what its statement makes — see `stampPoliciesStoredAsRendered`.
|
|
139
142
|
*/
|
|
140
|
-
function policyStampSql(plan) {
|
|
141
|
-
const hashes = renderedHashes(plan);
|
|
143
|
+
function policyStampSql(plan, only) {
|
|
144
|
+
const hashes = new Map([...renderedHashes(plan)].filter(([name]) => !only || only.has(name)));
|
|
142
145
|
if (hashes.size === 0) return void 0;
|
|
143
146
|
const target = `"${plan.schema}"."${plan.table}"`;
|
|
144
147
|
const values = [...hashes].map(([name, hash]) => `(${quoteSqlLiteral(name)}, '${hash}')`).join(", ");
|
|
@@ -206,6 +209,52 @@ async function readPolicyStamps(client, collections) {
|
|
|
206
209
|
}
|
|
207
210
|
return states;
|
|
208
211
|
}
|
|
212
|
+
/** The name a probed policy is made under, in a transaction nothing commits. */
|
|
213
|
+
var PROBE_POLICY = "rebase_policy_probe";
|
|
214
|
+
/**
|
|
215
|
+
* For each of `policies`, whether the `CREATE POLICY` the plan renders for it
|
|
216
|
+
* stores the definition the database holds now: made under a probe name on
|
|
217
|
+
* the same table, read back, compared with the one there, and rolled back.
|
|
218
|
+
*
|
|
219
|
+
* What a stamp cannot answer. A stamp covers the statement as rendered, so a
|
|
220
|
+
* rule whose text differs from the one that made the policy reads as changed
|
|
221
|
+
* though Postgres would store the same thing: `schema introspect` writes a role
|
|
222
|
+
* rule back as its deparsed SQL, and every such policy was planned as a
|
|
223
|
+
* replacement of itself. A policy `db migrate` created has no stamp at all.
|
|
224
|
+
*
|
|
225
|
+
* One short transaction per policy, which takes the table's lock for as long
|
|
226
|
+
* as it lasts and gives up on it after two seconds. A policy that could not be
|
|
227
|
+
* probed — no statement for it, a lock not granted, a rule that does not
|
|
228
|
+
* parse — is left out of the answer, and the caller keeps what the stamp said.
|
|
229
|
+
* Keyed `schema.table.name`.
|
|
230
|
+
*/
|
|
231
|
+
async function probeRenderedPolicies(client, collections, policies, options = {}) {
|
|
232
|
+
const statements = /* @__PURE__ */ new Map();
|
|
233
|
+
for (const plan of planCollectionPolicies(collections)) for (const statement of plan.policyStatements) {
|
|
234
|
+
const match = /^\s*CREATE POLICY\s+"([^"]+)"/i.exec(statement);
|
|
235
|
+
if (match) statements.set(`${plan.schema}.${plan.table}.${match[1]}`, statement.trim());
|
|
236
|
+
}
|
|
237
|
+
const answers = /* @__PURE__ */ new Map();
|
|
238
|
+
for (const policy of policies) {
|
|
239
|
+
const key = `${policy.schema}.${policy.table}.${policy.name}`;
|
|
240
|
+
const statement = statements.get(key);
|
|
241
|
+
if (!statement) continue;
|
|
242
|
+
const probe = statement.replace(/^(\s*CREATE POLICY\s+)"[^"]+"/i, `$1"${PROBE_POLICY}"`);
|
|
243
|
+
const target = `"${policy.schema.replace(/"/g, "\"\"")}"."${policy.table.replace(/"/g, "\"\"")}"`;
|
|
244
|
+
const definition = (name) => `(SELECT ${liveDefinitionSql("p")} FROM pg_policy p WHERE p.polrelid = ${quoteSqlLiteral(target)}::regclass AND p.polname = ${quoteSqlLiteral(name)})`;
|
|
245
|
+
try {
|
|
246
|
+
await client.query("BEGIN");
|
|
247
|
+
await client.query("SET LOCAL lock_timeout = '2s'");
|
|
248
|
+
if (options.searchPath) await client.query(`SET LOCAL search_path TO ${options.searchPath}`);
|
|
249
|
+
await client.query(probe);
|
|
250
|
+
const { rows } = await client.query(`SELECT ${definition(PROBE_POLICY)} IS NOT DISTINCT FROM ${definition(policy.name)} AS same`);
|
|
251
|
+
if (typeof rows[0]?.same === "boolean") answers.set(key, rows[0].same);
|
|
252
|
+
} catch {} finally {
|
|
253
|
+
await client.query("ROLLBACK").catch(() => void 0);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return answers;
|
|
257
|
+
}
|
|
209
258
|
/**
|
|
210
259
|
* Every declared schema's tables, and whether each has row-level security on.
|
|
211
260
|
*
|
|
@@ -597,6 +646,6 @@ async function giveUp(client, table, lastError, lock, result) {
|
|
|
597
646
|
});
|
|
598
647
|
}
|
|
599
648
|
//#endregion
|
|
600
|
-
export { describeRulesDropped, ensureCollectionPolicies, lockCollectionTables, policyStampSql, readPolicyStamps, tablesRlsWouldEnable };
|
|
649
|
+
export { describeRulesDropped, ensureCollectionPolicies, lockCollectionTables, policyStampSql, probeRenderedPolicies, readPolicyStamps, tablesRlsWouldEnable };
|
|
601
650
|
|
|
602
|
-
//# sourceMappingURL=ensure-collection-policies-
|
|
651
|
+
//# sourceMappingURL=ensure-collection-policies-BnGOURpG.js.map
|