@lucidcms/core
Version:
The core of the Lucid CMS. It's responsible for spinning up the API and serving the CMS.
1 lines • 11 kB
Source Map (JSON)
{"version":3,"file":"generate-migration-plan.mjs","names":["constants"],"sources":["../../../../src/libs/collection/migration/generate-migration-plan.ts"],"sourcesContent":["import constants from \"../../../constants/constants.js\";\nimport type { CollectionSchema } from \"../../../libs/collection/schema/types.js\";\nimport type DatabaseAdapter from \"../../../libs/db/adapter-base.js\";\nimport logger from \"../../../libs/logger/index.js\";\nimport type { InferredTable, ServiceResponse } from \"../../../types.js\";\nimport determineColumnModType from \"../helpers/column-mod-type.js\";\nimport getTablePriority from \"../helpers/get-table-priority.js\";\nimport indexesMatch from \"../helpers/indexes-match.js\";\nimport normaliseColumn from \"../helpers/normalise-column.js\";\nimport determineColumnMods from \"./determine-column-mods.js\";\nimport type {\n\tColumnOperation,\n\tIndexOperation,\n\tMigrationPlan,\n\tTableMigration,\n} from \"./types.js\";\n\nconst DISABLE_REMOVE_TABLES = true;\nconst PROTECT_NON_REMOVABLE_COLUMNS = true;\n\n/**\n * Diffs configured schema indexes against DB-inferred indexes, only removing\n * Lucid-generated names so manual database indexes are left alone.\n */\nconst createIndexOperations = (\n\tcurrentIndexes: CollectionSchema[\"tables\"][number][\"indexes\"],\n\texistingIndexes: InferredTable[\"indexes\"],\n): IndexOperation[] => {\n\tconst operations: IndexOperation[] = [];\n\tconst targetIndexes = existingIndexes ?? [];\n\tconst expectedIndexes = currentIndexes ?? [];\n\n\tfor (const index of expectedIndexes) {\n\t\tconst targetIndex = targetIndexes.find((item) => item.name === index.name);\n\t\tif (!targetIndex) {\n\t\t\toperations.push({\n\t\t\t\ttype: \"add\",\n\t\t\t\tindex,\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!indexesMatch(index, targetIndex)) {\n\t\t\tif (targetIndex.name.startsWith(constants.db.generatedIndexPrefix)) {\n\t\t\t\toperations.push({\n\t\t\t\t\ttype: \"remove\",\n\t\t\t\t\tindex: {\n\t\t\t\t\t\tname: targetIndex.name,\n\t\t\t\t\t\tcolumns: targetIndex.columns,\n\t\t\t\t\t\tunique: targetIndex.unique,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t}\n\t\t\toperations.push({\n\t\t\t\ttype: \"add\",\n\t\t\t\tindex,\n\t\t\t});\n\t\t}\n\t}\n\n\tfor (const index of targetIndexes) {\n\t\tconst indexStillExists = expectedIndexes.some(\n\t\t\t(item) => item.name === index.name,\n\t\t);\n\t\tif (indexStillExists) continue;\n\t\tif (!index.name.startsWith(constants.db.generatedIndexPrefix)) continue;\n\n\t\toperations.push({\n\t\t\ttype: \"remove\",\n\t\t\tindex: {\n\t\t\t\tname: index.name,\n\t\t\t\tcolumns: index.columns,\n\t\t\t\tunique: index.unique,\n\t\t\t},\n\t\t});\n\t}\n\n\treturn operations;\n};\n\n/**\n * Generates a migration plan for a collection\n */\nconst generateMigrationPlan = (props: {\n\tschemas: {\n\t\texisting: InferredTable[];\n\t\tcurrent: CollectionSchema;\n\t};\n\tdb: DatabaseAdapter;\n}): Awaited<ServiceResponse<MigrationPlan>> => {\n\tconst plan: MigrationPlan = {\n\t\tcollectionKey: props.schemas.current.key,\n\t\ttables: [],\n\t};\n\n\t//* if there is no existing schema, create a full migration plan\n\tif (props.schemas.existing.length === 0) {\n\t\tplan.tables = props.schemas.current.tables\n\t\t\t.map((table) => {\n\t\t\t\tconst tablePrioRes = getTablePriority(\"collection-inferred\", table);\n\t\t\t\tif (tablePrioRes.error) return null;\n\n\t\t\t\treturn {\n\t\t\t\t\ttype: \"create\",\n\t\t\t\t\ttableName: table.name,\n\t\t\t\t\ttableType: table.type,\n\t\t\t\t\tkey: table.key,\n\t\t\t\t\tpriority: tablePrioRes.data,\n\t\t\t\t\tcolumnOperations: table.columns.map((column) => ({\n\t\t\t\t\t\ttype: \"add\",\n\t\t\t\t\t\tcolumn: normaliseColumn(column, column.source),\n\t\t\t\t\t})),\n\t\t\t\t\tindexOperations: (table.indexes ?? []).map((index) => ({\n\t\t\t\t\t\ttype: \"add\",\n\t\t\t\t\t\tindex,\n\t\t\t\t\t})),\n\t\t\t\t} satisfies TableMigration;\n\t\t\t})\n\t\t\t.filter((t) => t !== null);\n\n\t\tlogger.debug({\n\t\t\tmessage: `Generated a full migration plan for collection \"${props.schemas.current.key}\"`,\n\t\t\tscope: constants.logScopes.migrations,\n\t\t});\n\n\t\treturn {\n\t\t\tdata: plan,\n\t\t\terror: undefined,\n\t\t};\n\t}\n\n\t//* create a partial migration plan\n\tfor (const table of props.schemas.current.tables) {\n\t\tconst targetTable = props.schemas.existing.find(\n\t\t\t(t) => t.name === table.name,\n\t\t);\n\t\tif (!targetTable) {\n\t\t\tconst tablePrioRes = getTablePriority(\"collection-inferred\", table);\n\t\t\tif (tablePrioRes.error) return tablePrioRes;\n\n\t\t\tplan.tables.push({\n\t\t\t\ttype: \"create\",\n\t\t\t\ttableName: table.name,\n\t\t\t\tpriority: tablePrioRes.data,\n\t\t\t\ttableType: table.type,\n\t\t\t\tkey: table.key,\n\t\t\t\tcolumnOperations: table.columns.map((column) => ({\n\t\t\t\t\ttype: \"add\",\n\t\t\t\t\tcolumn: normaliseColumn(column, column.source),\n\t\t\t\t})),\n\t\t\t\tindexOperations: (table.indexes ?? []).map((index) => ({\n\t\t\t\t\ttype: \"add\",\n\t\t\t\t\tindex,\n\t\t\t\t})),\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst columnOperations: ColumnOperation[] = [];\n\t\tconst indexOperations = createIndexOperations(\n\t\t\ttable.indexes,\n\t\t\ttargetTable.indexes,\n\t\t);\n\n\t\tfor (const column of table.columns) {\n\t\t\tconst targetColumn = targetTable.columns.find(\n\t\t\t\t(c) => c.name === column.name,\n\t\t\t);\n\n\t\t\tif (!targetColumn) {\n\t\t\t\tcolumnOperations.push({\n\t\t\t\t\ttype: \"add\",\n\t\t\t\t\tcolumn: column,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tconst modifications = determineColumnMods(\n\t\t\t\t\tnormaliseColumn(column, column.source),\n\t\t\t\t\tnormaliseColumn(targetColumn, column.source),\n\t\t\t\t);\n\t\t\t\tif (modifications) {\n\t\t\t\t\tconst modType = determineColumnModType(modifications, props.db);\n\n\t\t\t\t\tif (modType === \"drop-and-add\") {\n\t\t\t\t\t\tcolumnOperations.push({\n\t\t\t\t\t\t\ttype: \"remove\",\n\t\t\t\t\t\t\tcolumnName: modifications.column.name,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tcolumnOperations.push({\n\t\t\t\t\t\t\ttype: \"add\",\n\t\t\t\t\t\t\tcolumn: modifications.column,\n\t\t\t\t\t\t});\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcolumnOperations.push(modifications);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor (const column of targetTable.columns) {\n\t\t\tconst columnStillExists = table.columns.some(\n\t\t\t\t(c) => c.name === column.name,\n\t\t\t);\n\t\t\tif (columnStillExists) continue;\n\n\t\t\t// DB inferred columns do not include schema metadata, so we infer\n\t\t\t// custom-field columns from the generated prefix.\n\t\t\tconst canAutoRemove =\n\t\t\t\t!PROTECT_NON_REMOVABLE_COLUMNS ||\n\t\t\t\t!column.name.startsWith(constants.db.generatedColumnPrefix);\n\t\t\tif (!canAutoRemove) continue;\n\n\t\t\tcolumnOperations.push({\n\t\t\t\ttype: \"remove\",\n\t\t\t\tcolumnName: column.name,\n\t\t\t});\n\t\t}\n\n\t\tif (columnOperations.length || indexOperations.length) {\n\t\t\tconst tablePrioRes = getTablePriority(\"collection-inferred\", table);\n\t\t\tif (tablePrioRes.error) return tablePrioRes;\n\n\t\t\tplan.tables.push({\n\t\t\t\ttype: \"modify\",\n\t\t\t\ttableName: table.name,\n\t\t\t\tpriority: tablePrioRes.data,\n\t\t\t\ttableType: table.type,\n\t\t\t\tkey: table.key,\n\t\t\t\tcolumnOperations,\n\t\t\t\tindexOperations,\n\t\t\t});\n\t\t}\n\t}\n\n\tif (!DISABLE_REMOVE_TABLES) {\n\t\tfor (const table of props.schemas.existing) {\n\t\t\tconst tableStillExists = props.schemas.current.tables.some(\n\t\t\t\t(t) => t.name === table.name,\n\t\t\t);\n\t\t\tif (!tableStillExists) {\n\t\t\t\tconst tablePrioRes = getTablePriority(\"db-inferred\", table);\n\t\t\t\tif (tablePrioRes.error) return tablePrioRes;\n\n\t\t\t\tplan.tables.push({\n\t\t\t\t\ttype: \"remove\",\n\t\t\t\t\ttableName: table.name,\n\t\t\t\t\tpriority: tablePrioRes.data,\n\t\t\t\t\t// tableType: table.type,\n\t\t\t\t\t// key: table.key,\n\t\t\t\t\tcolumnOperations: [],\n\t\t\t\t\tindexOperations: [],\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\tif (plan.tables.length > 0) {\n\t\tlogger.debug({\n\t\t\tmessage: `Generated a partial migration plan for collection \"${props.schemas.current.key}\"`,\n\t\t\tscope: constants.logScopes.migrations,\n\t\t});\n\t}\n\n\treturn {\n\t\tdata: plan,\n\t\terror: undefined,\n\t};\n};\n\nexport default generateMigrationPlan;\n"],"mappings":"2TAwBA,MAAM,GACL,EACA,IACsB,CACtB,IAAM,EAA+B,CAAC,EAChC,EAAgB,GAAmB,CAAC,EACpC,EAAkB,GAAkB,CAAC,EAE3C,IAAK,IAAM,KAAS,EAAiB,CACpC,IAAM,EAAc,EAAc,KAAM,GAAS,EAAK,OAAS,EAAM,IAAI,EACzE,GAAI,CAAC,EAAa,CACjB,EAAW,KAAK,CACf,KAAM,MACN,OACD,CAAC,EACD,QACD,CAEK,EAAa,EAAO,CAAW,IAC/B,EAAY,KAAK,WAAWA,EAAU,GAAG,oBAAoB,GAChE,EAAW,KAAK,CACf,KAAM,SACN,MAAO,CACN,KAAM,EAAY,KAClB,QAAS,EAAY,QACrB,OAAQ,EAAY,MACrB,CACD,CAAC,EAEF,EAAW,KAAK,CACf,KAAM,MACN,OACD,CAAC,EAEH,CAEA,IAAK,IAAM,KAAS,EACM,EAAgB,KACvC,GAAS,EAAK,OAAS,EAAM,IAEZ,GACd,EAAM,KAAK,WAAWA,EAAU,GAAG,oBAAoB,GAE5D,EAAW,KAAK,CACf,KAAM,SACN,MAAO,CACN,KAAM,EAAM,KACZ,QAAS,EAAM,QACf,OAAQ,EAAM,MACf,CACD,CAAC,EAGF,OAAO,CACR,EAKM,EAAyB,GAMgB,CAC9C,IAAM,EAAsB,CAC3B,cAAe,EAAM,QAAQ,QAAQ,IACrC,OAAQ,CAAC,CACV,EAGA,GAAI,EAAM,QAAQ,SAAS,SAAW,EA6BrC,MA5BA,GAAK,OAAS,EAAM,QAAQ,QAAQ,OAClC,IAAK,GAAU,CACf,IAAM,EAAe,EAAiB,sBAAuB,CAAK,EAGlE,OAFI,EAAa,MAAc,KAExB,CACN,KAAM,SACN,UAAW,EAAM,KACjB,UAAW,EAAM,KACjB,IAAK,EAAM,IACX,SAAU,EAAa,KACvB,iBAAkB,EAAM,QAAQ,IAAK,IAAY,CAChD,KAAM,MACN,OAAQ,EAAgB,EAAQ,EAAO,MAAM,CAC9C,EAAE,EACF,iBAAkB,EAAM,SAAW,CAAC,EAAA,CAAG,IAAK,IAAW,CACtD,KAAM,MACN,OACD,EAAE,CACH,CACD,CAAC,CAAC,CACD,OAAQ,GAAM,IAAM,IAAI,EAE1B,EAAO,MAAM,CACZ,QAAS,mDAAmD,EAAM,QAAQ,QAAQ,IAAI,GACtF,MAAOA,EAAU,UAAU,UAC5B,CAAC,EAEM,CACN,KAAM,EACN,MAAO,IAAA,EACR,EAID,IAAK,IAAM,KAAS,EAAM,QAAQ,QAAQ,OAAQ,CACjD,IAAM,EAAc,EAAM,QAAQ,SAAS,KACzC,GAAM,EAAE,OAAS,EAAM,IACzB,EACA,GAAI,CAAC,EAAa,CACjB,IAAM,EAAe,EAAiB,sBAAuB,CAAK,EAClE,GAAI,EAAa,MAAO,OAAO,EAE/B,EAAK,OAAO,KAAK,CAChB,KAAM,SACN,UAAW,EAAM,KACjB,SAAU,EAAa,KACvB,UAAW,EAAM,KACjB,IAAK,EAAM,IACX,iBAAkB,EAAM,QAAQ,IAAK,IAAY,CAChD,KAAM,MACN,OAAQ,EAAgB,EAAQ,EAAO,MAAM,CAC9C,EAAE,EACF,iBAAkB,EAAM,SAAW,CAAC,EAAA,CAAG,IAAK,IAAW,CACtD,KAAM,MACN,OACD,EAAE,CACH,CAAC,EACD,QACD,CAEA,IAAM,EAAsC,CAAC,EACvC,EAAkB,EACvB,EAAM,QACN,EAAY,OACb,EAEA,IAAK,IAAM,KAAU,EAAM,QAAS,CACnC,IAAM,EAAe,EAAY,QAAQ,KACvC,GAAM,EAAE,OAAS,EAAO,IAC1B,EAEA,GAAI,CAAC,EACJ,EAAiB,KAAK,CACrB,KAAM,MACE,QACT,CAAC,MACK,CACN,IAAM,EAAgB,EACrB,EAAgB,EAAQ,EAAO,MAAM,EACrC,EAAgB,EAAc,EAAO,MAAM,CAC5C,EACI,IACa,EAAuB,EAAe,EAAM,EAElD,IAAM,gBACf,EAAiB,KAAK,CACrB,KAAM,SACN,WAAY,EAAc,OAAO,IAClC,CAAC,EACD,EAAiB,KAAK,CACrB,KAAM,MACN,OAAQ,EAAc,MACvB,CAAC,GAED,EAAiB,KAAK,CAAa,EAGtC,CACD,CAEA,IAAK,IAAM,KAAU,EAAY,QACN,EAAM,QAAQ,KACtC,GAAM,EAAE,OAAS,EAAO,IAEN,GAMlB,EAAO,KAAK,WAAWA,EAAU,GAAG,qBAAqB,GAG3D,EAAiB,KAAK,CACrB,KAAM,SACN,WAAY,EAAO,IACpB,CAAC,EAGF,GAAI,EAAiB,QAAU,EAAgB,OAAQ,CACtD,IAAM,EAAe,EAAiB,sBAAuB,CAAK,EAClE,GAAI,EAAa,MAAO,OAAO,EAE/B,EAAK,OAAO,KAAK,CAChB,KAAM,SACN,UAAW,EAAM,KACjB,SAAU,EAAa,KACvB,UAAW,EAAM,KACjB,IAAK,EAAM,IACX,mBACA,iBACD,CAAC,CACF,CACD,CA+BA,OAPI,EAAK,OAAO,OAAS,GACxB,EAAO,MAAM,CACZ,QAAS,sDAAsD,EAAM,QAAQ,QAAQ,IAAI,GACzF,MAAOA,EAAU,UAAU,UAC5B,CAAC,EAGK,CACN,KAAM,EACN,MAAO,IAAA,EACR,CACD"}