@lucidcms/core
Version:
The core of the Lucid CMS. It's responsible for spinning up the API and serving the CMS.
1 lines • 9.2 kB
Source Map (JSON)
{"version":3,"file":"assess-migration-plan.mjs","names":[],"sources":["../../../../src/libs/collection/migration/assess-migration-plan.ts"],"sourcesContent":["import type {\n\tAddColumnOperation,\n\tMigrationAssessment,\n\tMigrationPlan,\n\tMigrationRisk,\n\tMigrationRiskReason,\n\tModifyColumnOperation,\n\tTableMigration,\n} from \"./types.js\";\n\nconst riskPriority: Record<MigrationRisk, number> = {\n\tsafe: 0,\n\twarning: 1,\n\tdestructive: 2,\n};\n\n/** Returns the highest risk represented by a list of assessment reasons. */\nconst getHighestRisk = (reasons: MigrationRiskReason[]): MigrationRisk => {\n\treturn reasons.reduce<MigrationRisk>((highest, reason) => {\n\t\treturn riskPriority[reason.risk] > riskPriority[highest]\n\t\t\t? reason.risk\n\t\t\t: highest;\n\t}, \"safe\");\n};\n\n/** Assesses the constraints applied by a column added to an existing table. */\nconst assessAddedColumn = (\n\tplan: MigrationPlan,\n\ttable: TableMigration,\n\toperation: AddColumnOperation,\n): MigrationRiskReason => {\n\tconst constrained =\n\t\toperation.column.nullable === false ||\n\t\toperation.column.primary === true ||\n\t\toperation.column.unique === true ||\n\t\toperation.column.foreignKey !== undefined;\n\n\treturn {\n\t\trisk: constrained ? \"warning\" : \"safe\",\n\t\tcode: constrained ? \"add_constrained_column\" : \"add_nullable_column\",\n\t\tcollectionKey: plan.collectionKey,\n\t\ttableName: table.tableName,\n\t\tcolumnName: operation.column.name,\n\t};\n};\n\n/** Assesses in-place changes to one existing column. */\nconst assessModifiedColumn = (\n\tplan: MigrationPlan,\n\ttable: TableMigration,\n\toperation: ModifyColumnOperation,\n): MigrationRiskReason[] => {\n\tconst reasons: MigrationRiskReason[] = [];\n\tconst baseReason = {\n\t\tcollectionKey: plan.collectionKey,\n\t\ttableName: table.tableName,\n\t\tcolumnName: operation.column.name,\n\t};\n\n\tif (operation.changes.type) {\n\t\treasons.push({\n\t\t\t...baseReason,\n\t\t\trisk: \"destructive\",\n\t\t\tcode: \"modify_column_type\",\n\t\t});\n\t}\n\tif (operation.changes.unique || operation.changes.foreignKey) {\n\t\treasons.push({\n\t\t\t...baseReason,\n\t\t\trisk: \"destructive\",\n\t\t\tcode: \"modify_column_constraint\",\n\t\t});\n\t}\n\tif (operation.changes.nullable) {\n\t\treasons.push({\n\t\t\t...baseReason,\n\t\t\trisk: operation.changes.nullable.to ? \"safe\" : \"warning\",\n\t\t\tcode: operation.changes.nullable.to\n\t\t\t\t? \"relax_column_nullability\"\n\t\t\t\t: \"tighten_column_nullability\",\n\t\t});\n\t}\n\tif (\n\t\toperation.changes.default &&\n\t\t!operation.changes.type &&\n\t\t!operation.changes.unique &&\n\t\t!operation.changes.foreignKey\n\t) {\n\t\treasons.push({\n\t\t\t...baseReason,\n\t\t\trisk: \"safe\",\n\t\t\tcode: \"change_column_default\",\n\t\t});\n\t}\n\n\treturn reasons;\n};\n\n/** Assesses all column operations while collapsing drop-and-add recreations. */\nconst assessColumnOperations = (\n\tplan: MigrationPlan,\n\ttable: TableMigration,\n): MigrationRiskReason[] => {\n\tconst reasons: MigrationRiskReason[] = [];\n\tconst addedColumnNames = new Set(\n\t\ttable.columnOperations\n\t\t\t.filter((operation) => operation.type === \"add\")\n\t\t\t.map((operation) => operation.column.name),\n\t);\n\tconst recreatedColumnNames = new Set(\n\t\ttable.columnOperations\n\t\t\t.filter((operation) => operation.type === \"remove\")\n\t\t\t.map((operation) => operation.columnName)\n\t\t\t.filter((columnName) => addedColumnNames.has(columnName)),\n\t);\n\n\tfor (const operation of table.columnOperations) {\n\t\tconst columnName =\n\t\t\toperation.type === \"remove\"\n\t\t\t\t? operation.columnName\n\t\t\t\t: operation.column.name;\n\n\t\tif (recreatedColumnNames.has(columnName)) {\n\t\t\tif (operation.type === \"remove\") {\n\t\t\t\treasons.push({\n\t\t\t\t\trisk: \"destructive\",\n\t\t\t\t\tcode: \"recreate_column\",\n\t\t\t\t\tcollectionKey: plan.collectionKey,\n\t\t\t\t\ttableName: table.tableName,\n\t\t\t\t\tcolumnName,\n\t\t\t\t});\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tswitch (operation.type) {\n\t\t\tcase \"add\":\n\t\t\t\treasons.push(assessAddedColumn(plan, table, operation));\n\t\t\t\tbreak;\n\t\t\tcase \"modify\":\n\t\t\t\treasons.push(...assessModifiedColumn(plan, table, operation));\n\t\t\t\tbreak;\n\t\t\tcase \"remove\":\n\t\t\t\treasons.push({\n\t\t\t\t\trisk: \"destructive\",\n\t\t\t\t\tcode: \"remove_column\",\n\t\t\t\t\tcollectionKey: plan.collectionKey,\n\t\t\t\t\ttableName: table.tableName,\n\t\t\t\t\tcolumnName: operation.columnName,\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn reasons;\n};\n\n/** Assesses generated index additions and removals, including uniqueness. */\nconst assessIndexOperations = (\n\tplan: MigrationPlan,\n\ttable: TableMigration,\n): MigrationRiskReason[] => {\n\treturn table.indexOperations.map((operation) => {\n\t\tconst unique = operation.index.unique === true;\n\t\treturn {\n\t\t\trisk: unique ? \"warning\" : \"safe\",\n\t\t\tcode:\n\t\t\t\toperation.type === \"add\"\n\t\t\t\t\t? unique\n\t\t\t\t\t\t? \"add_unique_index\"\n\t\t\t\t\t\t: \"add_index\"\n\t\t\t\t\t: unique\n\t\t\t\t\t\t? \"remove_unique_index\"\n\t\t\t\t\t\t: \"remove_index\",\n\t\t\tcollectionKey: plan.collectionKey,\n\t\t\ttableName: table.tableName,\n\t\t\tindexName: operation.index.name,\n\t\t} satisfies MigrationRiskReason;\n\t});\n};\n\n/** Assesses a table operation and its nested column and index operations. */\nconst assessTable = (\n\tplan: MigrationPlan,\n\ttable: TableMigration,\n): MigrationRiskReason[] => {\n\tif (table.type === \"create\") {\n\t\treturn [\n\t\t\t{\n\t\t\t\trisk: \"safe\",\n\t\t\t\tcode: \"create_table\",\n\t\t\t\tcollectionKey: plan.collectionKey,\n\t\t\t\ttableName: table.tableName,\n\t\t\t},\n\t\t];\n\t}\n\tif (table.type === \"remove\") {\n\t\treturn [\n\t\t\t{\n\t\t\t\trisk: \"destructive\",\n\t\t\t\tcode: \"remove_table\",\n\t\t\t\tcollectionKey: plan.collectionKey,\n\t\t\t\ttableName: table.tableName,\n\t\t\t},\n\t\t];\n\t}\n\n\treturn [\n\t\t...assessColumnOperations(plan, table),\n\t\t...assessIndexOperations(plan, table),\n\t];\n};\n\n/**\n * Classifies collection migration operations by their highest potential impact.\n * Safe plans can run automatically, warnings require approval, and destructive\n * plans require explicit acknowledgement that stored data may be lost.\n */\nconst assessMigrationPlans = (plans: MigrationPlan[]): MigrationAssessment => {\n\tconst reasons = plans.flatMap((plan) =>\n\t\tplan.tables.flatMap((table) => assessTable(plan, table)),\n\t);\n\n\treturn {\n\t\trisk: getHighestRisk(reasons),\n\t\treasons,\n\t};\n};\n\n/** Returns the numeric ordering used when comparing migration risk levels. */\nexport const getMigrationRiskPriority = (risk: MigrationRisk): number =>\n\triskPriority[risk];\n\nexport default assessMigrationPlans;\n"],"mappings":"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"}